
双峰のデータを、ビン幅ひとつで単峰に消してみた。ヒストグラムとKDEの「粒度」が物語を書き換える
前回は「要約統計量が同じでも、散布図の形は全く別物になりうる」という話だった。今回は近い場所を別角度から突く。要約統計量どころか、可視化の方法自体が1個のパラメータで書き換わるという話だ。
ヒストグラムを描くとき、ビン幅は自分で決める。KDE(カーネル密度推定)を描くとき、帯域幅(バンド幅)も自分で決める——か、ライブラリのデフォルトに任せる。この「粒度」のつまみひとつで、同じデータが「山1つ」にも「山2つ」にも「山だらけ」にも見えることがある、というのは統計の教科書でよく言われる話だ。本当にそうなるのか、自分の手でサンプルを1つ引いて確かめてみた。
土台: 「本当は双峰」だが、ギリギリのギャップにした分布
平均−2.0、平均2.2、どちらも標準偏差1.0の正規分布を、重み0.5ずつで混ぜた。これが「真の分布」だ。
MU1, SD1 = -2.0, 1.0
MU2, SD2 = 2.2, 1.0
W = 0.5 # 混合比
この真の密度を実際に計算すると、谷の深さは山の高さの22%(谷/山比0.220)。離れすぎていて誰でも双峰だと分かる設定でも、近すぎて誰も双峰だと気づけない設定でもなく、「見方次第で双峰にも単峰にも見えかねない」ギリギリの深さを狙って選んだ。
この分布から220点だけをサンプリングする(seed=42)。谷/山比0.220とサンプル数220で数字がそろっているのは、狙ったわけではなくただの偶然だ。1万点あれば見方を変えてもだいたい同じ絵になるが、220点だと「どう見るか」の選択が結果を左右し始める——これくらいの規模感が今回の主役だ。

真の密度(オレンジの曲線)は誰が見ても双峰。だが下のラグを見ても、220本の縦線の並びだけから双峰だと即断できる人は少ないはずだ。
ヒストグラム: 同じ220点が、幅ひとつで別の顔になる
同じサンプルに、ビン幅だけ変えたヒストグラムを3種類重ねてみた。

実際のビンの中身はこうだった。なお、ビンはサンプルの最小値の少し外側から固定幅で敷き詰めているので、右端にサンプルが1つも入らない空のビン(0)が出ることがある。
- 幅2.4(5ビン):
[48, 66, 65, 41, 0]。見事に「山なりに増えて減る」1つの塊に見える。実際には2つ目・3つ目のビンの高さがほぼ同じ(66と65)で、そこに本物の谷が埋もれているのだが、ビンがそれをならしてしまう - 幅0.8(13ビン):
[5,16,27,37,20,9,11,19,35,24,11,6,0]。37→9→35という谷がくっきり出る。真の分布の形にかなり忠実 - 幅0.15(65ビン): 0〜12でバラバラ。ビンあたり平均3.4点しかないので、1〜2点の偶然の偏りがそのままギザギザの「山」になる。目視でピークを数えると20個以上——本物の山は2つしかないのに
同じ220個の数字が、ビン幅という1つのつまみで「単峰」「双峰」「山だらけ」の3つの別人になった。しかもどれも「もっともらしい」見た目をしているのが厄介なところで、幅2.4の図だけを渡されたら私はたぶん「まあ単峰かな」で片付けていたと思う。
KDE: なめらかな曲線も同じ罠にかかる
「ヒストグラムは階段状だからガタつくだけで、KDEならなめらかだから安心」と思いたいところだが、KDEも本質的には同じ罠にかかる。
その理由を見る前に、そもそもKDEが何をしているのかを押さえておきたい。KDEの正体は、各データ点の上に小さなガウスの山(核=カーネル)を1つずつ置き、その220個の山を全部足し合わせて1本の曲線にするという、それだけの仕組みだ。そして帯域幅(バンド幅)とは、この核1個1個の幅のこと。仕組みが見えるように、今回のサンプルの最初の12点だけを取り出して、「核」と「合計」の関係を描いてみた。

核が細ければ(上段)、点が集まっている場所だけが盛り上がり、合計には谷が残る。核が太ければ(下段)、隣どうしの山が大きく重なって、合計は1つの山にならされる。つまりヒストグラムでビン幅が担っていた「粒度」の役割を、KDEでは核の幅=帯域幅が担っている。なめらかに見えるのは階段がないからであって、粒度の選択から逃れられたわけではない。
もう1つ、このあと出てくる数字の読み方を先に書いておく。scipy.stats.gaussian_kdeのbw_methodに数値を渡すと、それは帯域幅そのものではなく**スケール因子(factor)**として扱われ、実際の帯域幅は「factor × サンプルの標準偏差」になる。今回のサンプルの標準偏差は2.41なので、たとえばfactor=0.15なら帯域幅は0.15×2.41≈0.36だ(以降は両方を併記する)。このfactorを変えて確かめた。

- factor=0.08(帯域幅0.19): 山の数7個。各点の周りに置いた小さなガウス核がほぼ独立に残り、点1個分のノイズがそのまま「山」として見える
- factor=0.15(帯域幅0.36): 山の数2個、谷/山比0.29。真の値0.220にかなり近い、素直な再現
- factor=0.8(帯域幅1.93): 山の数1個。谷が完全に埋まり、双峰だった痕跡すら残らない
ヒストグラムとKDEの違いは階段かなめらかかだけで、「粒度をどう選ぶか」という本質的な問題は共通していた。
GIFとスライダーで、粒度を連続的に動かしてみる
同じサンプルに対して、ヒストグラムのビン幅とKDEの帯域幅を粗い側から細かい側まで連続的にスイープしたのがこちら。

止まっているところだけ見ると、「粗い」と「細かい」はどちらも自信満々の1枚の完成した絵に見える。だがこうして地続きに動かすと、その自信が単に「たまたま選んだ粒度」の産物でしかないことがよく分かる。
止めて自分のペースで確認したい人向けに、同じスイープをスライダーにした版も用意した。
真ん中あたり(ステップ5前後)で双峰がもっともくっきりし、両端に近づくにつれて「単峰」または「ノイズだらけ」に崩れていくのが、自分の手で確認できるはずだ。
自動選択則(Scott則・Silverman則)は、フェアには見えなかった
ここまでの「適切な帯域幅」は私が手で探した値(factor=0.15)だ。実務では手で探さず、scipy.stats.gaussian_kde(x)とだけ書いてデフォルトに任せることが多い。デフォルトはScott則で、この標本ではfactor=0.340になる。参考にSilverman則も計算するとfactor=0.360、ほぼ同じ値だった。これは「自分がたまたま良い値を選んだだけ」という疑いを晴らすための、いわば答え合わせだ。

結果は正直、意外だった。
- 手動(factor=0.15): 谷/山比 0.29(真の値0.220に近い)
- Scott則(factor=0.340): 谷/山比 0.52
- Silverman則(factor=0.360): 谷/山比 0.56
自動選択則は双峰を完全に消しはしなかった。山は2つとも残っている。ただし谷の深さは手動選択の半分程度しかなく、真の形からはだいぶ遠い、ぼんやりした「肩がある1つの山」に近い印象の曲線になった。自動選択則はサンプル数と分散から一般的に無難な値を出す設計になっていて、この標本のように「双峰だが谷が浅くギリギリの分布」に対しては、双峰を検出はするが強調はしてくれない——という、ちょうど中間の残念な結果だった。「デフォルトに任せれば安全」とも「デフォルトは信用できない」とも言い切れない、その間のリアルな挙動を見た気がする。
ちなみにヒストグラムのビン幅にも自動則(Freedman-Diaconis則、Scott則)があり、この標本では幅1.4付近を推奨してくる。試しに幅1.5で切ってみると[17, 61, 34, 21, 60, 24, 3]となり、こちらは谷(21)と山(61・60)の差がはっきり出ていた。同じ「自動選択則」でも、ヒストグラム側の方がこの標本には相性が良かったことになる。
手を動かして意外だったこと
一番意外だったのは、KDEの自動選択則の「中途半端さ」だ。てっきり「デフォルトなら大体うまくいく」か「デフォルトは往々にして甘すぎる」のどちらかだろうと予想していたが、実際は「山は残すが谷は削る」という、失敗と呼ぶには惜しい、成功と呼ぶには物足りない結果だった。自動化されたツールの出力を鵜呑みにする前に、少なくとも1回はパラメータを手で振ってみる価値があると実感した。
そしてもう1つ、粒度をひとつ変えるだけで物語が変わるという構造は、データ分析の外でもよく見る気がする。人間関係のすれ違いも、プロジェクトの進み具合も、誰かの行動の変化も、見る時間の窓を短くすれば「ただのノイズ」に見え、長くしすぎれば「均された1本のトレンド」に見え、ちょうどよい窓でだけ「本当の起伏」が見える。データの場合はビン幅や帯域幅を意図的に何通りか試せば済むが、日常の判断では自分がどの粒度で見ているかを自覚すること自体が難しい。せめてデータに向き合うときくらいは、1つの粒度だけで結論を出さないようにしたい。
まとめ
- 平均−2.0/2.2、標準偏差1.0の正規分布を重み0.5ずつで混ぜた「真の谷/山比0.220」の分布から220点をサンプリングした
- ヒストグラムはビン幅2.4で双峰が単峰に埋没(
[48,66,65,41,0])、幅0.8で双峰がくっきり(37→9→35)、幅0.15でノイズだらけの20個以上の偽ピークになった - KDEも同じ構造で、factor=0.08は山7個のノイズ、factor=0.15は山2個で谷/山比0.29(真値0.220に近い)、factor=0.8は完全に単峰化した
- scipyデフォルトのScott則(factor=0.340)・Silverman則(factor=0.360)はどちらも双峰は残すが、谷/山比は0.52〜0.56と手動選択の約2倍浅く、真の形からは離れた「ぼんやりした双峰」になった
- 同じデータでも、ビン幅・帯域幅という1つのつまみで「単峰」「双峰」「ノイズだらけ」のどれにでも見える。可視化を1パターンだけで済ませないことが、この手の見落としへの一番シンプルな対策だと思う


