箱ひげ図関連の重要語をクイックアクセス:
🍰 まずはやさしく
データのまとめ役のような図です。
ばらつきや外れ値をひと目で知るために使います。
部活のメンバーの身長を比べる時に便利です。
図に込められた5つの要素について読みましょう。
箱ひげ図(box plot, box-and-whisker plot)は、 分布の中央値・四分位・外れ値を1つの図で表現する強力な可視化。 Tukey が1977年に体系化。
SSDSE 食料費の場合:Q1 = 76.0、 中央値 = 79.9、 Q3 = 85.1、 IQR = 9.1千円。
💡 箱ひげ図1枚で 中央値・ばらつき・歪み・外れ値 がすべて分かります。 探索的データ分析(EDA)の主力ツール。
🍰 まずはやさしく
データの分布をシンプルに見せる図です。
グループごとの違いを比べるために使います。
地域ごとの食費の差を調べる時に役立ちます。
定義から実装までの6つの視点で解説します。
論文中に 「箱ひげ図」として登場する用語。
箱ひげ図 とは:中央値・四分位・外れ値をひと目で見せる図。複数群の分布比較に最適。
本ページでは「boxplot」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「boxplot」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
下の左パネルはデータ点の並んだ「帯」です。 点をドラッグすると値が変わり、 空いた場所をタップ/クリックすると点が増え、 点をタップすると消えます(動かさなければ削除、 動かせば移動)。 プリセットには SSDSE-B-2026(2023 年・47 都道府県)の実測値を用意しました。 データを動かすたびに 五数要約(最小・Q1・中央値・Q3・最大)・IQR・1.5×IQR フェンス・外れ値が上のミニ箱ひげ図でリアルタイムに再描画され、 右のヒストグラムと 同じデータの 2 つの表現を並べて見比べられます。
💡 体感ポイント: 「外れ値を1つ追加」を押すと、 極端な1点でも 中央値と IQR(箱)はほとんど動かないのに、 ◆平均は外れ値に引っ張られて移動します。 これが「中央値・IQR は頑健、 平均・標準偏差は外れ値に弱い」という頑健統計量の核心です。 逆に箱の中央付近の点を動かしても五数要約はほとんど変わらず、 箱ひげ図が分布の「骨格」だけを要約していることが分かります。 SSDSE 消費支出のプリセットでは 1 県(愛媛)が下側フェンスの外に落ち、 実データでも外れ値が自然に現れることを確認できます。
🍰 まずはやさしく
箱ひげ図をアレンジした仲間たちです。
データの量や目的に合わせて使い分けます。
少人数のテスト結果を点で見せる方法などがあります。
より詳しく分析するための派生図を紹介します。
個別のデータ点をひげの位置にずらして並べる。 サンプルサイズが小さい時に有効:
中央値の95%信頼区間を「くびれ」で表示。 群間の重なりで有意差が判断できる:
大量データ用の拡張箱ひげ図。 複数階層のパーセンタイルを表示:
密度が高い場所で点を横に広げて、 重ならないように配置するプロット。 小〜中規模データに最適。
箱ひげ図は「5 数要約(最小・Q1・中央・Q3・最大)を 1 つの箱で示す」。 群間比較に強く、 平均は出さない。 SSDSE-B-2026 で A1101 の箱ひげを描くと、 Q1=103.4 万、 中央=154.9 万、 Q3=263.7 万、 上ヒゲ閾値=504 万を超える東京〜北海道の 9 都道府県が「外れ値」点として描画される。
箱ひげ図 は「可視化」カテゴリの中核概念。 初めて触れる読者は、 まずこの「🎨 直感」セクションだけ通読し、 必要になった時点で「📐 数式」「🐍 Python」「⚠️ 落とし穴」へ戻る読み方が定着しやすいです。
🍰 まずはやさしく
箱ひげ図を作るためのルールです。
正確な位置を計算するために使います。
スマホの利用時間のばらつきを数値で出します。
ひげの長さを決める数式について読みましょう。
直感の次は、 厳密な定義を確認します。 数式は言語の一種で、 一度書き慣れれば「言葉より速く伝えられる」便利な道具。 慣れていない方は、 各記号が何を表すかを下の「🔬 数式を言葉で読み解く」で 1 つずつ確認してください。
上の数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。
47都道府県の「L3221 消費支出(二人以上の世帯、 円/月)」(2023 年度)を地方ブロックごとに集計し、 五数要約・IQR・外れ値を取り出して箱ひげ図を描く。
📤 実行例: 横向きの箱 上から地方ブロック ラベルが見やすい
💬 読み方: カテゴリ数が多い(10 超)と縦向き表示は見づらい。 横向き + sorted で並べ替えれば順位も把握しやすい。 ラベル長 > 5 文字なら水平推奨。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | import pandas as pd import numpy as np import matplotlib.pyplot as plt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 2023 年度の 47 都道府県 def region(code): num = int(str(code).lstrip('R')) // 1000 # 'R01000' → 1 if num == 1: return '北海道' if 2 <= num <= 7: return '東北' if 8 <= num <= 14: return '関東' if 15 <= num <= 23: return '中部' if 24 <= num <= 30: return '近畿' if 31 <= num <= 35: return '中国' if 36 <= num <= 39: return '四国' return '九州沖縄' df['地域'] = df['Code'].apply(region) # 後続例で使う派生列(実在列から導出・リネーム) df['高齢化率'] = df['A1303'] / df['A1101'] * 100 df['都市規模'] = np.where(df['A1101'] >= 2e6, '200万人以上', '200万人未満') df = df.rename(columns={'L322101': '食料費', 'L322102': '住居費', 'L322108': '教育費'}) # 五数要約と IQR を取り出す print(df.groupby('地域')['L3221'] .describe(percentiles=[.25, .5, .75]) .round(0)) # 箱ひげ図を描く fig, ax = plt.subplots(figsize=(10, 5)) order = df.groupby('地域')['L3221'].median().sort_values().index data = [df.loc[df['地域'] == r, 'L3221'] for r in order] # matplotlib 3.9 以降、boxplot の labels= は tick_labels= に変わった ax.boxplot(data, tick_labels=order, showmeans=True, meanprops=dict(marker='D', markerfacecolor='red')) ax.set_ylabel('L3221 消費支出(二人以上の世帯、円/月)') ax.set_title('地方ブロック別 消費支出の分布(SSDSE-B-2026, 2023)') ax.grid(alpha=.3, axis='y') plt.tight_layout(); plt.savefig('boxplot_region.png', dpi=140) |
典型的な観察例(2023 年度の実測): 関東ブロックは中央値 307,817 円と 8 ブロック中最高で、 最大は埼玉の 344,092 円。 九州沖縄は中央値 276,996 円と最も低い。 四国は最小の愛媛 223,423 円を含み標準偏差 35,879 円と最もばらつきが大きい。 同じ「中央値」でも分布形状が違うことが一目で分かる。 平均(赤ダイヤ)と中央値(オレンジ線)が乖離していれば歪度の存在を示唆(例:四国は平均 276,528 円 < 中央値 290,838 円で左に歪む)。
📤 実行例: 地方ブロック × 都市規模 で 14 箱(北海道・四国は片側のみ) ほとんどのブロックで 200 万人未満の県の方が高齢化率高(例:東北 35.2 vs 29.2)
💬 読み方: hue で詳細な比較が可能だが、 箱数が増えると見づらい。 凡例を明示。 サブグループ間の関係性に着目するなら point plot や bar plot も検討。
1 2 3 4 5 6 7 8 9 10 11 | def detect_outliers(s, k=1.5): q1, q3 = s.quantile(.25), s.quantile(.75) iqr = q3 - q1 lo, hi = q1 - k*iqr, q3 + k*iqr return s[(s < lo) | (s > hi)] for r, g in df.groupby('地域'): out = detect_outliers(g['L3221']) if len(out): print(f'{r}: 外れ値 {len(out)} 件') print(g.loc[out.index, ['Prefecture', 'L3221']].to_string(index=False)) |
💬 ブロック内の 1.5×IQR ルールでは、 中部の福井(276,690 円)と四国の愛媛(223,423 円)がいずれも下側の外れ値として検出される。
箱ひげ図の Q1・中央値・Q3・IQR・上下ひげ閾値を、 SSDSE-B-2026 の A1101(人口、 2023 年)で実際に計算します。 47 都道府県の値を昇順に並べ、 四分位を取り、 IQR × 1.5 で外れ値閾値を求めるという一連の手順を電卓的に追体験。 Python の numpy.percentile と完全一致するか確認しましょう。
SSDSE-B-2026 (2023) の A1101 で実値計算:Q1=1,034,000、 中央 1,549,000、 Q3=2,636,500、 IQR=1,602,500、 上ヒゲ閾値=2,636,500 + 1.5×1,602,500 = 5,040,250。 これを超える 9 都道府県(東京 14,086,000/神奈川 9,229,000/大阪 8,763,000/愛知 7,477,000/埼玉 7,331,000/千葉 6,257,000/兵庫 5,370,000/福岡 5,103,000/北海道 5,092,000)が点として描画される。
| 都道府県 | A1101 総人口 | A1303 65 歳以上 | L3221 消費支出 |
|---|---|---|---|
| 東京都 | 14,086,000 | 3,205,000 | 341,320 |
| 神奈川県 | 9,229,000 | 2,390,000 | 306,565 |
| 大阪府 | 8,763,000 | 2,424,000 | 271,246 |
| 愛知県 | 7,477,000 | 1,923,000 | 300,221 |
| 埼玉県 | 7,331,000 | 2,012,000 | 344,092 |
| 千葉県 | 6,257,000 | 1,756,000 | 306,943 |
上記は SSDSE-B-2026 (2023) からの抜粋。 手計算で確認した値が、 後述の Python 実装で得る値と一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。
合成データ x = [3, 5, 8, 2, 7, 12, 4] (n=7、 多様な整数) を使って Q1・中央値・Q3・IQR・外れ値判定 (1.5×IQR ルール) を Step 1〜4 で展開し、 Python (numpy) で再現する。 手計算と Python の結果が一致することを確認する。
$$ \mathrm{IQR} = Q_3 - Q_1, \quad \text{外れ値} = \big\{\, x : x < Q_1 - 1.5\,\mathrm{IQR} \;\text{or}\; x > Q_3 + 1.5\,\mathrm{IQR} \,\big\} $$
| サンプル | 値 x |
|---|---|
| x₁ | 3 |
| x₂ | 5 |
| x₃ | 8 |
| x₄ | 2 |
| x₅ | 7 |
| x₆ | 12 |
| x₇ | 4 |
| 項目 | 計算 | 結果 |
|---|---|---|
| ソート後 | — | [2, 3, 4, 5, 7, 8, 12] |
| 中央値 Median の位置 | (n+1)/2 = (7+1)/2 | 4 番目 = 5 |
| Q1 の位置 (線形補間) | 0.25 × (n−1) = 0.25 × 6 = 1.5 → index 1 と 2 の中点 | 3 + (4−3)×0.5 = 3.5 |
| Q3 の位置 (線形補間) | 0.75 × (n−1) = 0.75 × 6 = 4.5 → index 4 と 5 の中点 | 7 + (8−7)×0.5 = 7.5 |
numpy.percentile デフォルト (linear) と同じ補間方式。
| 項目 | 計算 | 結果 |
|---|---|---|
| IQR | Q3 − Q1 = 7.5 − 3.5 | 4.0 |
| 下限 (lower fence) | Q1 − 1.5 × IQR = 3.5 − 1.5×4.0 = 3.5 − 6.0 | −2.5 |
| 上限 (upper fence) | Q3 + 1.5 × IQR = 7.5 + 1.5×4.0 = 7.5 + 6.0 | 13.5 |
| 値 | 判定 (−2.5 ≤ x ≤ 13.5) | 外れ値? |
|---|---|---|
| 2 | −2.5 ≤ 2 ≤ 13.5 | × |
| 3 | −2.5 ≤ 3 ≤ 13.5 | × |
| 4 | −2.5 ≤ 4 ≤ 13.5 | × |
| 5 | −2.5 ≤ 5 ≤ 13.5 | × |
| 7 | −2.5 ≤ 7 ≤ 13.5 | × |
| 8 | −2.5 ≤ 8 ≤ 13.5 | × |
| 12 | −2.5 ≤ 12 ≤ 13.5 | × |
最大値 12 は上限 13.5 を超えないため、 1.5×IQR ルール上は外れ値ではない (上ヒゲがそこに伸びる)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import numpy as np x = np.array([3, 5, 8, 2, 7, 12, 4]) q1 = np.percentile(x, 25) median = np.percentile(x, 50) q3 = np.percentile(x, 75) iqr = q3 - q1 lower = q1 - 1.5 * iqr upper = q3 + 1.5 * iqr outliers = x[(x < lower) | (x > upper)] print(f"ソート後 = {np.sort(x)}") print(f"Q1 = {q1}") print(f"Median = {median}") print(f"Q3 = {q3}") print(f"IQR = {iqr}") print(f"下限 = {lower}") print(f"上限 = {upper}") print(f"外れ値 = {outliers.tolist()}") |
💬 手計算 (Step 2: Q1=3.5, Median=5, Q3=7.5 / Step 3: IQR=4, 下限=−2.5, 上限=13.5 / Step 4: 外れ値なし) と Python 出力が完全一致。 numpy.percentile デフォルトの線形補間で再現できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import matplotlib.pyplot as plt import inspect # 1変数 plt.boxplot(df['食料費']) # 複数変数 # 目盛りのラベルは boxplot の引数名が版によって違う(labels → tick_labels)。 # plt.xticks で後から付ければ、どの版でも同じように動く。 _cols = ['食料費', '住居費', '教育費'] plt.boxplot([df[c] for c in _cols]) plt.xticks(range(1, len(_cols) + 1), _cols) # 横向き(matplotlib 3.10 で vert=False は orientation='horizontal' に変わった) _p = inspect.signature(plt.boxplot).parameters _horiz = {'orientation': 'horizontal'} if 'orientation' in _p else {'vert': False} plt.boxplot(df['食料費'], **_horiz) # notched + patch_artist で色付け bp = plt.boxplot(data, notch=True, patch_artist=True) for patch in bp['boxes']: patch.set_facecolor('lightblue') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import seaborn as sns # 1変数 sns.boxplot(y=df['食料費']) # 群比較 sns.boxplot(x='地域', y='食料費', data=df) # 入れ子の比較 sns.boxplot(x='地域', y='食料費', hue='都市規模', data=df) # 個別点と重ねる sns.boxplot(x='地域', y='食料費', data=df) sns.stripplot(x='地域', y='食料費', data=df, color='black', alpha=0.5) |
1 2 3 4 5 6 | import plotly.express as px fig = px.box(df, x='地域', y='食料費', points='all', # 個別点も表示 hover_data=['Prefecture']) fig.show() |
機械学習の前処理で、 全特徴量の分布を箱ひげ図で確認。 「異常な値」「単位の違い」「スケーリング必要性」を判定。
複数モデルの CV スコアを箱ひげ図で比較:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.preprocessing import StandardScaler from sklearn.model_selection import cross_val_score # ── この抜粋だけで動くように、分類用の X, y を作る ── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023] X = StandardScaler().fit_transform( _d[['A4101', 'A1303', 'L3221']].astype(float)) # 出生数・65歳以上人口・消費支出 y = (_d['A1101'] > _d['A1101'].median()).astype(int) # 総人口が中央値より多いか # XGBoost はブラウザ版 Python に入っていないので、 # 同じ勾配ブースティング系の sklearn GradientBoostingClassifier で代用する scores_lr = cross_val_score(LogisticRegression(max_iter=1000), X, y, cv=10) scores_rf = cross_val_score(RandomForestClassifier(random_state=0), X, y, cv=10) scores_gb = cross_val_score(GradientBoostingClassifier(random_state=0), X, y, cv=10) sns.boxplot(data=[scores_lr, scores_rf, scores_gb]) plt.xticks([0, 1, 2], ['LR', 'RF', 'GBDT']) plt.ylabel('CV Score') for name, s in [('LR', scores_lr), ('RF', scores_rf), ('GBDT', scores_gb)]: print(f'{name}: 平均 {s.mean():.3f} (±{s.std():.3f})') |
同じハイパーパラメータで複数回学習し、 結果を箱ひげ図に。 安定性が分かる。
IQR ベースの自動外れ値検出は、 最もシンプルで実用的な手法の1つ。
箱ひげ図は5数要約しか見せないため、 二峰性(2つのピーク)は見えません。 バイオリンプロットや ヒストグラムを併用。
n=10 のデータと n=10000 のデータが同じ箱ひげ図になりえます。 サンプル数を別途記載しましょう(例:軸ラベルに n=○)。
ひげの外側 = 外れ値ではない。 「外れ値の可能性がある点」程度の意味。 ドメイン知識で確認を。
matplotlib のデフォルトは Tukey ルールだが、 他ソフトでは違うことも。 説明文に「Tukey の 1.5×IQR ルール」と明記。
裾の長い分布だと、 外れ値が多すぎて箱が潰れて見えなくなる。 log スケールで描くのも検討。
50年以上の歴史を持ち、 EDA・統計報告・機械学習の標準ツールとして使われ続けています。
📤 実行例: 高齢化率の外れ値(2023 年度、 47 都道府県) 下側: 東京(22.8%)・沖縄(23.8%) 上側: なし(最大の秋田 39.1% は上限 40.0% の内側) 2 都県のみ
💬 読み方: 外れ値は誤データかも実態かを判断。 SSDSE 公的データでは実態。 z-score>3 と IQR ルールで結果が異なる場合は分布形状を確認。 削除でなく log 変換も検討。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import pandas as pd import matplotlib.pyplot as plt # ── この抜粋だけで動くように、地方ブロック別の消費支出をまとめる ── _d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) _d = _d[_d['SSDSE-B-2026'] == 2023].copy() def _blk(code): n = int(str(code)[1:3]) return ('北海道' if n == 1 else '東北' if n <= 7 else '関東' if n <= 14 else '中部' if n <= 23 else '近畿' if n <= 30 else '中国' if n <= 35 else '四国' if n <= 39 else '九州沖縄') _d['地域'] = _d['Code'].map(_blk) _g = _d.groupby('地域')['L3221'] labels = list(_g.groups.keys()) data = [_g.get_group(k).values for k in labels] fig, ax = plt.subplots(figsize=(9, 5)) # ラベルは set_xticklabels で付ける # (boxplot の引数名は matplotlib の版で labels / tick_labels と違うため) bp = ax.boxplot(data, notch=True, patch_artist=True, whis=1.5, showmeans=True, medianprops=dict(color='black', lw=2), meanprops=dict(marker='D', mfc='red', mec='black')) ax.set_xticks(range(1, len(labels) + 1)) ax.set_xticklabels(labels, rotation=20) for patch, color in zip(bp['boxes'], plt.cm.Set2(range(len(data)))): patch.set_facecolor(color) ax.set_ylabel('消費支出(二人以上の世帯、円/月)') ax.grid(alpha=.3, axis='y') plt.tight_layout() plt.show() |
📤 実行例: 8 ブロック × 箱ひげ図 自動グルーピング タイトル: 'Boxplot grouped by 地域'
💬 読み方: pandas の boxplot は seaborn より見た目が地味だが手軽。 column を複数指定すれば複数変数を並列表示。 細かいカスタマイズは matplotlib/seaborn 推奨。
1 2 3 4 5 6 7 8 9 10 | import seaborn as sns fig, axes = plt.subplots(1, 3, figsize=(15, 4.5)) sns.boxplot(data=df, x='地域', y='L3221', ax=axes[0]) sns.violinplot(data=df, x='地域', y='L3221', inner='quartile', ax=axes[1]) sns.boxplot(data=df, x='地域', y='L3221', ax=axes[2]) sns.stripplot(data=df, x='地域', y='L3221', color='black', size=4, alpha=.6, ax=axes[2]) for ax in axes: ax.tick_params(axis='x', rotation=30) plt.tight_layout() |
📤 実行例: count=47, mean=31.6, std=3.3(高齢化率、 2023 年度) min=22.8, 25%=30.1, 50%=31.8, 75%=34.0, max=39.1 歪度=-0.6(左に裾を引く)
💬 読み方: 中央値(31.8)と平均(31.6)は近いが、 歪度 -0.6 で低い側に裾。 SD=3.3 に対し IQR/1.35 ≈ 2.9 → SD の方が大きく、 東京・沖縄の低値側の裾が SD を押し上げている。 ズレが大きいと歪み大。
1 2 3 4 5 | import plotly.express as px fig = px.box(df, x='地域', y='L3221', points='all', hover_data=['Prefecture']) fig.update_layout(title='地方ブロック別 消費支出の分布') fig.write_html('boxplot.html') |
📤 実行例: 対数軸の箱(2023 年度) 中央値≈155万、 鳥取≈54万、 東京≈1409万 オーダー差が見やすい
💬 読み方: スケール差 30 倍超なら対数軸が有効。 線形軸では東京の外れ値が支配的で他県が潰れる。 対数軸では全県が比較しやすい。 軸ラベルに「(log scale)」明記。
1 2 3 4 5 6 7 8 9 10 11 | from scipy import stats import numpy as np x = df['L3221'].values print('min :', np.min(x)) print('Q1 :', np.percentile(x, 25)) print('Median:', np.median(x)) print('Q3 :', np.percentile(x, 75)) print('max :', np.max(x)) print('IQR :', stats.iqr(x)) print('mean :', np.mean(x)) print('SD :', np.std(x, ddof=1)) |
ノッチ幅は中央値 ± 1.57·IQR/√n。 ノッチが重ならない群対は中央値が有意に異なる目安(簡易検定)。
📤 実行例: 左から低い順(2023 年度の中央値) 関東(28.1)→ 近畿(30.0)→ 中部(31.6)→ … → 四国(34.8)→ 東北(35.1) 順位が一目瞭然
💬 読み方: アルファベット順より中央値順の方が比較しやすい。 トレンドが見える。 順位の解釈には注意(中央値 vs 平均値 vs 最大値)。
1 2 3 4 5 6 7 8 9 | fig, ax = plt.subplots(figsize=(9, 5)) # ラベルは set_xticklabels で付ける(版によって labels / tick_labels と違うため) ax.boxplot(data, notch=True, bootstrap=10000, showmeans=True, whis=1.5) ax.set_xticks(range(1, len(labels) + 1)) ax.set_xticklabels(labels, rotation=20) ax.set_title('ノッチ付き箱ひげ(中央値95%CI)') plt.tight_layout() plt.show() |
公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで 箱ひげ図 を動作させます。 まずはこのまま実行してみてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # 箱ひげ図 を SSDSE-B-2026 で実行する最小コード import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023] # 2023 年のみ抽出 print(df.shape) # (47, 112) print(df[['Prefecture','A1101','A1303','L3221']].head()) import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(7,5)) # matplotlib 3.9 以降、boxplot の labels= は tick_labels= に改名された ax.boxplot(df['A1101'], tick_labels=['A1101 総人口']) ax.set_title('SSDSE-B-2026 (2023): A1101 箱ひげ図') q1, q3 = df['A1101'].quantile([0.25,0.75]) iqr = q3 - q1 print(f'Q1={q1:.0f} Q3={q3:.0f} IQR={iqr:.0f}') print(f'上ヒゲ閾値={q3 + 1.5*iqr:.0f}') plt.savefig('boxplot_demo.png', dpi=100) |
▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install matplotlib numpy pandas scikit-learn scipy seaborn が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。
箱ひげ図は分布を「四分位 + ひげ」に要約するため、 二山分布(例:男女混合の身長)でも単一の箱に潰される。 ヒストグラム・カーネル密度プロット・バイオリン図と併用しないと、 重要なクラスター構造を見逃す。 教育の現場でこれが原因で「平均だけ見て大丈夫」と誤判断する事例は後を絶たない。 とくに介入効果のサブグループ差を箱ひげのみで判断するのは危険。 必ず生データの散布も重ねる(strip plot / swarm plot 併用)。
「北海道(n=1)」と「関東(n=7)」と「九州沖縄(n=8)」の箱ひげを並べると、 サンプル数の極端な差が分からない。 北海道は箱の代わりに 1 点しかプロットできず、 ノイズで判断する事故が起きる。 群ごとの n を箱の下に明記する、 violinplot で帯幅にサンプル数を反映する、 ノッチ付き箱ひげで中央値の信頼区間を示す、 のいずれかで補強する。 n が極端に小さい群は注釈で警告するのが親切。
matplotlib の whis 既定値は 1.5、 R の base boxplot も 1.5、 seaborn は matplotlib をラップ。 一方で「ひげ=最小・最大」と単純定義する実装(Excel 既定や Tableau の一部)もある。 同じデータでも見た目が変わるので、 図の凡例に「whisker = Q1 - 1.5·IQR / Q3 + 1.5·IQR」と明記。 論文ではしばしばこの定義の違いから「外れ値が違う」と査読で指摘される。 自分の使うライブラリの既定値を必ず確認すること。
IQR ルールで「自動的に外れ値」と判定されても、 それは「分布の裾」を意味するだけで「異常値」とは限らない。 都道府県データで東京・大阪が外れるのは経済構造上当然であり、 削除すると本質情報が失われる。 外れ値はまず原因を調べ、 (a) 計測ミスなら修正、 (b) 異常な状況の真値なら残す、 (c) 主分析と感度分析を並走、 という流れが正道。 「箱ひげで外れ値と表示された=削除」と短絡しない。
箱ひげ図の箱(IQR)の重なり具合は統計検定の有無とは別物。 IQR が重なっていても t 検定で有意になることはあるし、 逆も真。 群間比較を主張するならノッチ付き箱ひげ(ノッチが重ならない=中央値が有意に異なる目安)か、 t 検定 / Mann-Whitney U の p 値を併記する。 「箱が分離して見えた」だけで結論しない。
箱ひげ図でも Y 軸の範囲操作は強力な「嘘の図」を生む。 たとえば「平均給与の地方差」を見せたいときに Y 軸を 250-350 に切ると群間差が大きく見え、 0-400 にすると小さく見える。 学術論文では原則「ゼロ起点」もしくは「データ範囲全体」を表示すべきだが、 商業プレゼンでは恣意的な切り詰めが横行する。 自分が作る側でも見る側でも、 Y 軸スケールを常にチェックする習慣をつける。
箱ひげ図で初学者が陥る典型ミス。 外れ値点を即「異常」と判定する、 群間比較で標本サイズの違いを無視する、 など。 SSDSE-B-2026 で 47 都道府県を可視化すると東京・大阪が必ず外れ値マークになり、 「除外すべきか保持すべきか」の判断が頻出します。
箱ひげ図 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 記述統計 › 可視化 › 箱ひげ図
中心に 箱ひげ図 を置き、 そこから ヒストグラム・散布図・中央値・四分位・外れ値検出・正規分布 など 計 9 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「箱ひげ図」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「箱ひげ図」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは 箱ひげ図 の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 可視化 → 箱ひげ図 という入れ子の位置を示します。 「可視化には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
箱ひげ図 (Tukey 1977) は 5 数要約 (最小・Q1・中央・Q3・最大) と外れ値を一目で示すロバスト可視化で、 群間比較と分布形の把握を兼ねる。
SSDSE-B-2026 で 47 都道府県の高齢化率を 8 地方区分で箱ひげ図化すると、 中央値の地方差・四国/東北の高さ・関東の幅広さ・沖縄の外れ値が一画面で読める。
「boxplot」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| データが大規模 (n が多い、 高次元) | 計算コスト / メモリ / 並列化が必要 | 標準手法 等 |
| 外れ値が多い / ノイズあり | 頑健性 / 外れ値除去 / 中央値ベース | 高速版 等 |
| 解釈性を重視する | 線形 / 木構造 / ルールベース | 頑健版 等 |
| 予測精度を最優先する | アンサンブル / ハイパラ調整 / 交差検証 | 高次元対応版 等 |
| データが少ない | 正則化 / ベイズ / 転移学習 / 簡素なモデル | 小データ向け 等 |
| リアルタイム/オンライン処理 | ストリーミング / 軽量モデル / 逐次更新 | 解釈重視版 等 |
箱ひげ図は、 データの全点を捨ててたった 5 つの位置(最小・第 1 四分位 Q1・中央値・第 3 四分位 Q3・最大)だけを残す「究極の要約」です。 なぜこの 5 点で十分なのか。 それは分布を語るとき私たちが本当に知りたいのが 中心はどこか(中央値)・どれだけ散らばるか(IQR)・左右どちらに歪んでいるか(中央値の箱内の偏り)・裾はどこまで伸びるか(ひげ)・例外はあるか(点) の 5 つに集約されるからです。
実データ L322101(食料費・二人以上の世帯・千円/月)の五数要約は次の通りです(numpy.percentile の線形補間・実測値)。
| 量 | 値(千円/月) | 図のどこに現れるか |
|---|---|---|
| 最小 | 71.1 | 下ひげの先端 |
| Q1(下から 25%) | 76.0 | 箱の下辺 |
| 中央値(50%) | 79.9 | 箱の中の太線 |
| Q3(下から 75%) | 85.1 | 箱の上辺 |
| 最大 | 97.8 | 上ひげの先端 |
| IQR = Q3 − Q1 | 9.1 | 箱の高さ(中央 50% の幅) |
| 上側フェンス Q3+1.5·IQR | 98.7 | 外れ値判定の上限 |
最大 97.8 千円は上側フェンス 98.7 千円の内側なので、 食料費に 外れ値は 1 件も出ません。 一方、 平均 80.6 千円は中央値 79.9 千円よりわずかに大きく、 これは分布がごく弱く右に歪んでいる(高い側に長い裾がある)ことを示します。 中央値と平均の差 +0.7 千円という小さなズレを箱ひげ図(◆平均マーカー付き)で読み取れるのが、 この図の解像度です。
箱ひげ図は強力ですが、 要約である以上、 必ず何かを捨てています。 上の「⚠️ 6 つの典型ミス」を踏まえ、 実務で誤読につながりやすい論点を実データとともにもう一段掘り下げます。
五数要約は分位点の位置しか持たないため、 山が 2 つある分布(二峰性)と 1 つの分布が同じ箱ひげ図になりえます。 例えば都市部と地方が混在した変数は「中央に谷、 両端に山」という形をとりがちですが、 箱ひげ図ではただの 1 つの箱に潰れます。 分布の形そのものを見たいときは、 同じデータを ヒストグラム や KDE(カーネル密度推定)で必ず併用します。 バイオリンプロット(箱ひげ + KDE)は、 まさにこの弱点を補うために設計された派生図です。
Tukey の係数 1.5 は「正規分布ならデータの約 99.3%(±2.7σ 相当)が内側に入る」という経験則から選ばれた任意の定数です。 3.0 にすれば「far out(極端な外れ値)」の判定になります。 同じ SSDSE の消費支出 L3221(実測)で係数を変えると判定は次のように変わります。
| 係数 k | 下側フェンス Q1−k·IQR | 外れ値として点になる県 |
|---|---|---|
| 1.5(標準) | 237.5 千円 | 愛媛県(223.4)の 1 件 |
| 3.0(far out) | 195.5 千円 | なし(愛媛も内側) |
Q1=279.5・Q3=307.5・IQR=28.0(いずれも実測)。 「愛媛が外れ値かどうか」は係数の選び方だけで反転します。 外れ値の点は「異常」ではなく「Tukey 規約上フェンスの外に出た候補」に過ぎません。 外れ値・外れ値処理のページも参照し、 削除ではなく対数変換や頑健統計量での対応も検討します。
n=5 の箱ひげ図と n=5000 の箱ひげ図は見た目で区別できません。 この教材の SSDSE 地方ブロック比較でも、 北海道は n=1、 中国は n=5、 中部は n=9 と大きく違うのに、 箱の見た目は「信頼度の差」を語りません。 n が小さいと Q1・Q3 自体の推定誤差が大きく、 箱の位置がたまたまである可能性が高まります。 対策は (a) 軸ラベルに n を明記、 (b) ストリップ/スウォームで個別点を重ねる、 (c) ノッチ(後述)で中央値の信頼区間を示す、 の 3 つです。
「ひげの先端」が何を指すかは実装で割れます。 (a) Tukey 型=フェンス内の実データの最小・最大(matplotlib・seaborn の既定)、 (b) min-max 型=外れ値を出さず単純に最小値・最大値まで伸ばす、 (c) パーセンタイル型=2–98% 点や 9–91% 点まで、 (d) 標準偏差型=平均 ± n·SD。 同じデータでも図の印象が変わるため、 図には必ず「Tukey の 1.5×IQR ルール」などひげの定義を明記します。
箱ひげ図の主役は中央値で、 既定では平均を描きません。 しかし歪んだ分布では平均と中央値が乖離します。 実測の消費支出 L3221 では 平均 295.9 千円 < 中央値 300.7 千円(差 −4.8 千円)で、 左に長い裾(低い側の県)が平均を押し下げています。 showmeans=True で◆平均を重ねると、 平均・中央値のズレ=歪みを一目で確認できます。
複数群を横に並べたとき、 箱の重なり具合を直感的な有意差判定に使いがちですが、 これは正確ではありません。 中央値の差の検定は箱の重なりではなく、 ノッチ(中央値の信頼区間)や t 検定・ANOVA・Kruskal-Wallis 検定で行うべきです。 また群ごとに n が違えば、 同じ箱幅でも情報量が違います。
箱ひげ図の弱点(形状・標本数・信頼区間が見えない)を補う派生図と、 五数要約の背後にある統計理論を整理します。 それぞれ「何を追加で見せるか」を一言で言えるようにしておくと、 場面に応じて選べます。
| 図 | 箱ひげに何を足すか | 向いている場面 |
|---|---|---|
| バイオリンプロット | 左右対称の KDE(密度)で分布の形 | 多峰性・歪みを見せたい・中〜大規模 |
| スウォーム(swarm) | 個別点を重ならないよう横に配置 | n が小さく全点を見せたい |
| ビースワーム(beeswarm) | 密度の高い所で点を横に広げる | 小〜中規模・点の密集を可視化 |
| ストリップ(strip) | 個別点をランダムジッタで配置 | 手軽に全点を重ねたい |
| レターバリュー(boxen) | 多段のパーセンタイルを階段状に | 大量データで裾を詳しく |
(本サイトにバイオリン図・スウォーム図の個別ページはまだ無いため、 ここではテキストで整理しています。 実装例はこのページの「🐍 Python」節の sns.violinplot / sns.swarmplot を参照。)
ノッチ(notch)は中央値の約 95% 信頼区間を箱のくびれとして描きます。 区間は 中央値 ± 1.57 · IQR / √n で近似されます。 実測の食料費(n=47・IQR=9.1・中央値 79.9)で計算すると、
2 群のノッチ(くびれ)が重ならなければ、 中央値に α=0.05 水準の差があると目安として読めます。 式に √n が入るため、 n が大きいほどくびれは細く(信頼区間が狭く)なり、 標本サイズの効果を図に取り込めます(落とし穴 3 の部分的な対策)。 ただし正確な検定としては Wilcoxon 順位和検定などを併用します。
箱ひげ図は John Tukey が 1977 年の『探索的データ解析(EDA)』で体系化しました。 五数要約は分布に正規性などの仮定を置かず、 順序統計量(並べ替えた順位)だけで中心と散らばりを表す点が本質です。 このため 正規分布から大きく外れた実データや、 各種分布が混ざったデータでも安定して機能します。 平均・標準偏差・分散が 1 点の極値で大きく動くのに対し、 中央値・IQR は頑健です。
箱ひげ図のフェンス(Q1−1.5·IQR / Q3+1.5·IQR)は、 そのまま IQR 法による外れ値検出アルゴリズムです。 z-score 法(平均 ± 3σ)が平均・標準偏差を使うため外れ値自身に引っ張られやすいのに対し、 IQR 法は中央値・四分位ベースで頑健です。 詳細は 外れ値・外れ値処理を参照。 実測の総人口 A1101 では上側フェンス 504 万人を超える 9 都道府県(東京 1409 万など)が点になりますが、 これは「異常」ではなく大都市圏という分布の右裾そのものです。
箱ひげ図の真価は群を横に並べる比較にあります。 中央値の高さ・箱の重なり・裾の長さ・外れ値の偏りを一望でき、 平均だけでは見えない「ばらつきの差」を捉えられます。 比較で差の有無を定量化したくなったら、 2 群なら t 検定(正規性が疑わしければ Mann-Whitney U)、 3 群以上なら ANOVA(同 Kruskal-Wallis)へ進むのが定石です。 箱ひげ図は「まず眺めて仮説を立てる」EDA、 検定は「その仮説を確かめる」確認統計、 という役割分担で使い分けます。