KDE와 히스토그램 — 분포 시각화
이 토픽을 마치면
히스토그램과 KDE 플롯의 차이를 설명할 수 있고, seaborn으로 데이터 분포를 시각화하여 데이터의 모양을 파악할 수 있습니다.
분포를 보는 이유
데이터를 분석할 때 평균과 표준편차만으로는 부족합니다:
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)**으로 나누고, 각 구간에 포함된 데이터 개수를 막대 높이로 표현합니다.
import matplotlib.pyplot as pltimport 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 수에 따라 모양이 달라집니다:
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, 커널 밀도 추정)는 히스토그램의 부드러운 버전입니다. 각 데이터 포인트에 작은 종 모양 곡선(커널)을 놓고 전부 합칩니다.
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 함께 보기
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 곡선과 스케일이 맞습니다.
두 그룹 비교
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는 겹치는 부분이 투명하게 보여서 두 그룹의 차이와 겹침을 직관적으로 파악할 수 있습니다.
다양한 분포 형태
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(대역폭)입니다. 각 데이터 포인트에 놓는 커널의 너비를 결정합니다:
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)를 발견하는 데 효과적입니다:
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 — 통합 분포 시각화
# 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 파라미터로 그룹별 비교도 간단하게 할 수 있습니다.
분포를 시각화하는 것은 데이터 분석의 첫 번째 단계입니다. 평균과 표준편차라는 숫자만으로는 놓치는 패턴이 그래프 한 장으로 보입니다.