K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

密度プロット

入門

06.2.2

密度プロット

最終更新 2020-02-12 読了時間 2 分
まとめ
密度プロットは、1 つの数値列の各データ点に幅の決まった小さな山(カーネル)を置き、その合計を 1 本の滑らかな曲線にして分布の形を見せる図です。ヒストグラムのように階級の切れ目を決める必要がない代わりに、山の幅(バンド幅)が曲線の滑らかさを決めます。sns.kdeplot(data=df, x="sepal_length") を列ごとに呼べば Iris の 4 変数を 1 枚に重ねられ、「花弁の長さは山が 2 つ、がく片の幅は 1 つ」のような形の違いが線で比べられます。読み終えると、bw_adjust を 0.5 と 2 で描き比べて形が安定する幅を選び、0 以下に伸びた裾のような推定の副作用を clip や cut で抑えられるようになります。

直感 #

アヤメ 150 個の「がく片の長さ」をヒストグラムにすると、階級の幅を 0.5 cm にするか 0.3 cm にするかで山の形が変わり、どちらが本当の形か迷います。密度プロットは階級を作りません。150 個の点それぞれの位置に、同じ幅・同じ高さの小さな釣鐘型の山を置き、150 個の山を全部足し合わせて 1 本の曲線にします。点が集まっている場所では山が重なって高くなり、点が少ない場所では低くなるので、曲線の高さがそのまま「データの密度」になります。階級の切れ目に左右されない反面、置く山の幅を広くすると曲線は滑らかになりすぎて 2 つの山が 1 つにつながり、狭くすると点ごとのギザギザが残ります。

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:密度曲線は「各点に置いた小さな山の合計」。階級の切れ目に左右されない代わりに、山の幅(バンド幅)が形を決める。

Python で描く #

次のコードは Iris の 4 つの測定値それぞれに sns.kdeplot を呼び、色を変えて同じ軸に 4 本の密度曲線を重ねます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import matplotlib.pyplot as plt
import seaborn as sns

fig, ax = plt.subplots(figsize=(6, 4))
df = sns.load_dataset("iris")

for column, color in zip(["sepal_length", "sepal_width", "petal_length", "petal_width"], ["#2563eb", "#0ea5e9", "#22c55e", "#f97316"]):
    sns.kdeplot(data=df, x=column, ax=ax, color=color, label=column)

ax.set_xlabel("測定値")
ax.set_ylabel("密度")
ax.set_title("Iris データの密度プロット")
ax.legend()
ax.grid(alpha=0.2)

fig.tight_layout()

plt.show()

密度プロットの図

実行結果の読み方 #

petal_width(オレンジ)は 0.2 cm 付近に鋭い山があり、1〜2.5 cm に低い山が続く 2 山の形です。petal_length(緑)も 1.5 cm と 4〜5 cm の 2 山で、どちらも setosa とそれ以外の品種が混ざっているサインです。sepal_width(水色)は 3 cm 付近に 1 つの山だけで、品種で分けられない変数だと読めます。曲線は 0 以下にもわずかに伸びていますが、これは山の裾がはみ出した推定の副作用で、長さが負のデータが存在するわけではありません。

使いどころと落とし穴 #

密度プロットが向くのは、100 点以上あるデータの形を複数系列で重ねて比べたいときです。20 点程度しかないデータでは、バンド幅の選び方で山の数が変わってしまい、形を信じられないので ヒストグラム か ECDF に切り替えます。落とし穴は 2 つあります。第一に、bw_adjust が小さすぎるとギザギザ、大きすぎると特徴が潰れるので、既定値の前後で描き比べる必要があります。第二に、KDE は 0 以上しか取らないデータでも負の領域に密度を伸ばします。価格や長さのような列では clip=(0, None) か cut=0 を渡して裾をデータ範囲で切ります。