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 は広く分布しています。分布を可視化すると、このような違いがわかります。
ヒストグラム — 棒で頻度を見る
ヒストグラムは、データを ビン に分割し、各ビンに含まれるデータの個数を棒の高さで表現します。
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 個のビンに分割することを意味します。ビンの数によって形状が異なります。
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()- ビンが少なすぎると: 詳細な構造が失われる
- ビンが多すぎると: ノイズが見える
- 適切なビンの数を見つけることが、ヒストグラムの課題です。
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 の利点:
- ビンの数を選択する必要がない (連続的な曲線であるため)
- 視覚的に滑らか — 分布の形状を直感的に把握
- 2 つの分布の比較が容易 — 重ねて描画するときに明確
ヒストグラム + 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 曲線とスケールが一致します。
2 つのグループを比較する
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 は、重なる部分が透明に表示されるため、2 つのグループの違いと重なりを直感的に把握できます。
さまざまな分布の形状
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('右に偏った分布')
# 二峰性分布 — 2 つのグループが混ざっている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()分布の形状を見ると、データの特性をすぐに知ることができます。二峰性分布が現れると、「データの中に 2 つのグループが混ざっている」という洞察を得ることができます。
ヒストグラム vs KDE の選択基準
| ヒストグラム | KDE | |
|---|---|---|
| 正確な頻度 | ✅ 各ビンの個数を確認可能 | ❌ 密度推定値 |
| 分布の形状 | ビンの数によって異なる | ✅ 一貫性のある滑らかな曲線 |
| グループの比較 | 重なると読み取りにくい | ✅ 透明なオーバーレイが明確 |
| 離散データ | ✅ 適切 | ❌ 連続データに適している |
| 少量データ | ✅ 実際のデータを反映 | ⚠️ 過度の平滑化のリスク |
両方を一緒に使うのが最も安全です — ヒストグラムで実際の分布を確認し、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 を 1 つの 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 パラメータでグループごとの比較を簡単に行うことができます。
分布を可視化することは、データ分析の最初のステップです。平均と標準偏差という数字だけでは見落としてしまうパターンが、グラフ 1 枚でわかります。