BioPlayground

🧬
목록으로

KDE와 히스토그램 — 분포 시각화

히스토그램과 KDE(커널 밀도 추정)의 차이를 이해하고, seaborn으로 데이터 분포를 시각화하는 방법을 배웁니다.

중급
|
10
|
검증 완료 (2026-07)
KDE히스토그램분포 시각화커널 밀도 추정데이터 분포
진행률0/17 (0%)

KDE와 히스토그램 — 분포 시각화

이 토픽을 마치면

히스토그램과 KDE 플롯의 차이를 설명할 수 있고, seaborn으로 데이터 분포를 시각화하여 데이터의 모양을 파악할 수 있습니다.


분포를 보는 이유

데이터를 분석할 때 평균과 표준편차만으로는 부족합니다:

python
import numpy as np
data_a = np.array([50, 50, 50, 50, 50])
data_b = np.array([10, 30, 50, 70, 90])
print(f"A 평균: {data_a.mean()}, B 평균: {data_b.mean()}") # 둘 다 50

평균은 같지만 데이터의 모양은 완전히 다릅니다. A는 한 곳에 몰려있고, B는 넓게 퍼져있습니다. 분포를 시각화하면 이런 차이가 보입니다.


히스토그램 — 막대로 빈도 보기

히스토그램은 데이터를 **구간(bin)**으로 나누고, 각 구간에 포함된 데이터 개수를 막대 높이로 표현합니다.

python
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
data = np.random.normal(170, 10, 1000) # 평균 170, 표준편차 10, 1000명의 키
plt.figure(figsize=(8, 4))
plt.hist(data, bins=30, color='steelblue', edgecolor='white', alpha=0.7)
plt.xlabel('키 (cm)')
plt.ylabel('빈도')
plt.title('키 분포 (히스토그램)')
plt.show()

bins=30은 전체 범위를 30개 구간으로 나눈다는 뜻입니다. bin 수에 따라 모양이 달라집니다:

python
fig, axes = plt.subplots(1, 3, figsize=(12, 3))
for ax, b in zip(axes, [5, 30, 100]):
ax.hist(data, bins=b, color='steelblue', edgecolor='white')
ax.set_title(f'bins={b}')
plt.tight_layout()
plt.show()
  • bins가 너무 적으면: 세부 구조가 사라짐
  • bins가 너무 많으면: 노이즈가 보임
  • 적절한 bin 수 찾기가 히스토그램의 과제입니다

KDE — 부드러운 곡선으로 밀도 보기

KDE(Kernel Density Estimation, 커널 밀도 추정)는 히스토그램의 부드러운 버전입니다. 각 데이터 포인트에 작은 종 모양 곡선(커널)을 놓고 전부 합칩니다.

python
import seaborn as sns
plt.figure(figsize=(8, 4))
sns.kdeplot(data, fill=True, color='steelblue', alpha=0.5)
plt.xlabel('키 (cm)')
plt.title('키 분포 (KDE)')
plt.show()

KDE의 장점:

  • bin 수를 선택할 필요 없음 (연속 곡선이므로)
  • 시각적으로 부드러움 — 분포의 모양을 직관적으로 파악
  • 두 분포 비교가 쉬움 — 겹쳐 그릴 때 명확

히스토그램 + KDE 함께 보기

python
plt.figure(figsize=(8, 4))
sns.histplot(data, bins=30, kde=True, color='steelblue',
edgecolor='white', alpha=0.5, stat='density')
plt.xlabel('키 (cm)')
plt.title('히스토그램 + KDE')
plt.show()

stat='density'를 설정하면 히스토그램의 y축이 빈도가 아닌 밀도로 바뀌어서 KDE 곡선과 스케일이 맞습니다.


두 그룹 비교

python
np.random.seed(42)
male = np.random.normal(175, 8, 500)
female = np.random.normal(162, 7, 500)
plt.figure(figsize=(8, 4))
sns.kdeplot(male, fill=True, label='남성', alpha=0.4)
sns.kdeplot(female, fill=True, label='여성', alpha=0.4)
plt.xlabel('키 (cm)')
plt.legend()
plt.title('성별 키 분포 비교')
plt.show()

KDE는 겹치는 부분이 투명하게 보여서 두 그룹의 차이와 겹침을 직관적으로 파악할 수 있습니다.


다양한 분포 형태

python
np.random.seed(42)
fig, axes = plt.subplots(1, 4, figsize=(16, 3))
# 정규 분포 — 좌우 대칭 종 모양
normal = np.random.normal(0, 1, 1000)
sns.kdeplot(normal, ax=axes[0], fill=True)
axes[0].set_title('정규 분포')
# 오른쪽 꼬리 — 대부분 작고 소수가 큼 (소득 분포)
skewed = np.random.exponential(2, 1000)
sns.kdeplot(skewed, ax=axes[1], fill=True)
axes[1].set_title('오른쪽 치우침')
# 쌍봉 분포 — 두 그룹이 섞여있음
bimodal = np.concatenate([np.random.normal(-2, 0.5, 500),
np.random.normal(2, 0.5, 500)])
sns.kdeplot(bimodal, ax=axes[2], fill=True)
axes[2].set_title('쌍봉 분포')
# 균일 분포 — 모든 값이 비슷한 빈도
uniform = np.random.uniform(0, 10, 1000)
sns.kdeplot(uniform, ax=axes[3], fill=True)
axes[3].set_title('균일 분포')
plt.tight_layout()
plt.show()

분포의 모양을 보면 데이터의 특성을 바로 알 수 있습니다. 쌍봉 분포가 나타나면 "데이터 안에 두 개의 그룹이 섞여있구나"라는 통찰을 얻습니다.


히스토그램 vs KDE 선택 기준

히스토그램KDE
정확한 빈도✅ 각 구간의 개수 확인 가능❌ 밀도 추정치
분포 모양bin 수에 따라 달라짐✅ 일관된 부드러운 곡선
그룹 비교겹치면 읽기 어려움✅ 투명 오버레이가 명확
이산 데이터✅ 적합❌ 연속 데이터에 적합
소규모 데이터✅ 실제 데이터 반영⚠️ 과도한 평활화 위험

두 가지를 함께 쓰는 것이 가장 안전합니다 — 히스토그램으로 실제 분포를 확인하고, KDE로 전체 경향을 파악합니다.



KDE의 bandwidth 파라미터

KDE에서 가장 중요한 설정은 bandwidth(대역폭)입니다. 각 데이터 포인트에 놓는 커널의 너비를 결정합니다:

python
fig, axes = plt.subplots(1, 3, figsize=(12, 3))
bandwidths = [0.1, 0.5, 2.0]
for ax, bw in zip(axes, bandwidths):
sns.kdeplot(data, bw_adjust=bw, ax=ax, fill=True)
ax.set_title(f'bw_adjust={bw}')
plt.tight_layout()
plt.show()
  • bandwidth 작음 (0.1): 노이즈까지 반영, 울퉁불퉁
  • bandwidth 적절 (0.5~1.0): 데이터의 경향을 잘 포착
  • bandwidth 큼 (2.0): 과도하게 평활화, 세부 구조 소실

seaborn의 bw_adjust는 자동 계산된 bandwidth에 곱하는 계수입니다. 1.0이 기본값이고, 대부분의 경우 잘 작동합니다.


실전 — 이상치 발견

분포 시각화는 이상치(outlier)를 발견하는 데 효과적입니다:

python
np.random.seed(42)
normal_data = np.random.normal(100, 15, 1000)
outliers = np.array([200, 210, 220, -50])
data_with_outliers = np.concatenate([normal_data, outliers])
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# 히스토그램으로 이상치 확인
axes[0].hist(data_with_outliers, bins=50, color='steelblue', edgecolor='white')
axes[0].set_title('히스토그램 — 꼬리에 이상치 보임')
# 박스플롯과 함께 보면 더 명확
axes[1].boxplot(data_with_outliers, vert=False)
axes[1].set_title('박스플롯 — 이상치가 점으로 표시')
plt.tight_layout()
plt.show()

KDE나 히스토그램에서 본 분포의 꼬리가 예상보다 길면 이상치를 의심합니다. 박스플롯과 함께 사용하면 더 정확한 진단이 가능합니다.



seaborn의 displot — 통합 분포 시각화

python
# displot은 히스토그램, KDE, ECDF를 하나의 API로 제공
sns.displot(data, kind="hist", kde=True, bins=30, height=4, aspect=2)
plt.show()
# ECDF — 누적 분포 함수
sns.displot(data, kind="ecdf", height=4, aspect=2)
plt.show()

displot은 seaborn 0.11+에서 도입된 통합 인터페이스입니다. kind 파라미터로 "hist", "kde", "ecdf" 중 선택하고, hue 파라미터로 그룹별 비교도 간단하게 할 수 있습니다.


분포를 시각화하는 것은 데이터 분석의 첫 번째 단계입니다. 평균과 표준편차라는 숫자만으로는 놓치는 패턴이 그래프 한 장으로 보입니다.