本ページでは、 順位ベースの記述統計を統合的に解説します。 四分位・四分位範囲 (IQR)・パーセンタイル・箱ひげ図・外れ値検出を一気通貫で扱います。
これらは外れ値に頑健な指標で、 歪んだ分布や外れ値を含むデータの記述に最適です。 SSDSE-B のように地域差が大きいデータの可視化で頻出します。
論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
SSDSE-B でも東京都の人口密度は他の46県と比べて極端な値で、 平均は引っ張られるが中央値は影響を受けにくい。
🌐 関連手法・派生:四分位は中央値を一般化したもの。 派生として 箱ひげ図、 上位概念として代表値、 並列概念として分散・標準偏差があります。
$p$-パーセンタイルとは「データの $p\%$ 以下にあたる値」。
標本点の中間にあたるパーセンタイルは内挿で計算。 NumPy には 9 種類の方法。
1 2 3 4 5 6 | import pandas as pd import numpy as np df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) x = df['一人当たり県民所得'] print('10%, 25%, 50%, 75%, 90% パーセンタイル:') print(np.percentile(x, [10, 25, 50, 75, 90]).round(0)) |
| 記号 | 名前 | 意味 |
|---|---|---|
| $Q_1$ | 第1四分位 | 25パーセンタイル(下位 1/4) |
| $Q_2$ | 第2四分位 | 中央値 |
| $Q_3$ | 第3四分位 | 75パーセンタイル(上位 1/4) |
1 | print(df['一人当たり県民所得'].describe()[['min','25%','50%','75%','max']]) |
9 個のソート済みデータ [10, 20, 30, 40, 50, 60, 70, 80, 90] の四分位:
$$\mathrm{IQR} = Q_3 - Q_1$$
$$\mathrm{MAD} = \mathrm{median}(|x_i - \tilde{x}|)$$
中央値からの絶対偏差の中央値。 最も頑健な「ばらつき指標」。 1.4826·MAD で正規分布の σ を推定できる。
1 2 3 4 5 | from scipy import stats mad = stats.median_abs_deviation(df['一人当たり県民所得']) print(f'MAD = {mad:.0f}') print(f'σ推定 (1.4826·MAD) = {1.4826*mad:.0f}') print(f'実 σ = {df["一人当たり県民所得"].std():.0f}') |
5数要約と外れ値を 1 枚に集約する可視化。 群間比較に最適。
ひげは「最大/最小 within フェンス」までで切る:
1 2 3 4 5 6 | import seaborn as sns import matplotlib.pyplot as plt df['都市規模'] = pd.qcut(df['人口密度'], q=3, labels=['農村','中規模','都市']) sns.boxplot(data=df, x='都市規模', y='一人当たり県民所得') plt.title('都市規模別の一人当たり県民所得') plt.show() |
箱ひげ + KDE(カーネル密度推定)。 分布形も同時に見える。
中央値の信頼区間をノッチで表示。 ノッチが重ならないなら 5% で中央値に有意差あり(簡易検定)。
$$\text{下限} = Q_1 - 1.5 \cdot \mathrm{IQR}, \quad \text{上限} = Q_3 + 1.5 \cdot \mathrm{IQR}$$
「3.0·IQR」を使うと「極端な外れ値」のみを検出。
1 2 3 4 5 6 | q1, q3 = df['人口密度'].quantile([0.25, 0.75]) iqr = q3 - q1 lower, upper = q1 - 1.5*iqr, q3 + 1.5*iqr outliers = df[(df['人口密度'] lower) | (df['人口密度'] > upper)] print(f'外れ値: {len(outliers)} 件') print(outliers[['都道府県', '人口密度']]) |
|z| > 3 を外れ値(正規分布なら 0.27%)。 ただし平均・σ が外れ値に引っ張られるため、 ロバスト Z-score(中央値・MAD)の方が良い。
1 2 3 4 | from sklearn.ensemble import IsolationForest iso = IsolationForest(contamination=0.05, random_state=42) df['outlier'] = iso.fit_predict(df[['一人当たり県民所得', '人口密度']]) print(df[df['outlier'] == -1][['都道府県']]) |
OLS は条件付き「平均」を推定するが、 分位点回帰は条件付き「分位点」を推定。 不均一分散・歪んだ分布で有用。
$$\hat{\boldsymbol{\beta}}_\tau = \arg\min_{\boldsymbol{\beta}} \sum_i \rho_\tau(y_i - \mathbf{x}_i^\top \boldsymbol{\beta})$$
$\rho_\tau(u) = u(\tau - \mathbb{1}[u < 0])$(pinball loss)。
1 2 3 4 | import statsmodels.formula.api as smf for tau in [0.1, 0.5, 0.9]: m = smf.quantreg('持ち家比率 ~ 一人当たり県民所得 + 高齢化率', data=df).fit(q=tau) print(f'τ={tau}: 所得={m.params["一人当たり県民所得"]:.4f}') |
| 指標 | 定義 | 頑健性 | 推奨 |
|---|---|---|---|
| 標準偏差 | $\sqrt{\mathbb{V}[X]}$ | 弱い | 正規分布 |
| IQR | $Q_3 - Q_1$ | 強い | 外れ値あり |
| MAD | $\mathrm{median}(|x - \tilde{x}|)$ | 最強 | 外れ値多 |
| 範囲 | max - min | 最弱 | 入門・簡易確認 |
| 変動係数 | $\sigma/\bar{x}$ | 弱い | スケール比較 |
| 落とし穴 | 対処 |
|---|---|
| パーセンタイル計算法の違いで結果ぶれ | interpolation 引数を明示し、 同一の方法で比較。 |
| 外れ値を機械的に削除 | 外れ値こそ重要情報のことも。 まず原因を調査。 |
| 小標本に Tukey ルール | n < 20 では信頼性低い。 個別検討。 |
| 箱ひげ図のひげと範囲を混同 | ひげは「フェンス内の最小/最大」、 範囲全体ではない。 |
| 標準偏差と IQR を「同じ」扱い | 正規分布なら IQR ≈ 1.349σ だが歪んでいると関係性なし。 |
| 対数スケールで箱ひげ図 | 変換後のスケールで作成し、 軸ラベルに明記。 |
| 5数要約だけで分布を語る | 同じ 5 数で異なる分布もある。 ヒストグラム併用。 |
1 2 3 4 5 6 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') summary = num.describe() gap = (summary.loc['mean'] - summary.loc['50%']) / summary.loc['std'] print(gap.abs().sort_values(ascending=False).head()) |
1 2 3 4 5 6 7 8 | for col in ['人口密度', '一人当たり県民所得']: q1, q3 = df[col].quantile([0.25, 0.75]) iqr = q3 - q1 lo, hi = q1 - 1.5*iqr, q3 + 1.5*iqr out = df[(df[col] lo) | (df[col] > hi)] print(f'{col}: 外れ値 {len(out)} 件') print(out[['都道府県', col]]) print() |
1 2 3 4 | import statsmodels.formula.api as smf for tau in [0.1, 0.5, 0.9]: m = smf.quantreg('持ち家比率 ~ 一人当たり県民所得', data=df).fit(q=tau) print(f'τ={tau}: β_所得 = {m.params["一人当たり県民所得"]:.4f}') |
「東京は外れ値なので除外しました。」
「人口密度の 5数要約は (最小 62, Q1 178, 中央 264, Q3 421, 最大 6,402)、 IQR = 243。 Tukey の上限 786 を超える観測値が 1 件(東京都)。 ただし東京都の高密度はデータの構造的特徴であり、 削除は不適切と判断。 代わりに log 変換(変換後の歪度 0.2)し、 平均ではなく中央値・IQR で記述、 回帰は分位点回帰で頑健に推定した。」
| 用途 | 関数 |
|---|---|
| パーセンタイル | np.percentile, pd.Series.quantile |
| 5数要約 | df.describe() |
| IQR | scipy.stats.iqr |
| MAD | scipy.stats.median_abs_deviation |
| 箱ひげ図 | matplotlib.pyplot.boxplot, seaborn.boxplot |
| バイオリン | seaborn.violinplot |
| 分位点回帰 | statsmodels.formula.api.quantreg |
| IsolationForest | sklearn.ensemble.IsolationForest |
| LOF | sklearn.neighbors.LocalOutlierFactor |
| ウィンザライズ | scipy.stats.mstats.winsorize |
標本データの累積確率:
$$\hat{F}_n(x) = \frac{1}{n}\sum_{i=1}^{n} \mathbb{1}[X_i \le x]$$
ECDF をプロットすれば、 全ての分位点・パーセンタイルを視覚的に読み取れる。
1 2 3 4 5 6 7 8 | import numpy as np, matplotlib.pyplot as plt x = np.sort(df['一人当たり県民所得'].values) ecdf = np.arange(1, len(x)+1) / len(x) plt.step(x, ecdf, where='post') plt.title('ECDF: 一人当たり県民所得') plt.xlabel('値'); plt.ylabel('累積確率') plt.grid(True) plt.show() |
パーセンタイル定義には複数あり、 NumPy/SciPy/R/Excel で結果が微妙に違う。
ブートストラップで分位点の 95% 信頼区間を推定:
1 2 3 4 | import numpy as np rng = np.random.default_rng(42) boot = [np.median(rng.choice(df['持ち家比率'], size=len(df), replace=True)) for _ in range(1000)] print(f'中央値 95% CI: [{np.percentile(boot, 2.5):.2f}, {np.percentile(boot, 97.5):.2f}]') |
順位ベースの相関係数。 外れ値・非線形関係に頑健。
1 | print(df[['一人当たり県民所得','人口密度']].corr(method='spearman')) |
9種類のメソッドで結果がどのくらい違うかを比較:
1 2 3 4 5 6 | import numpy as np x = df["一人当たり県民所得"].values for m in ['lower', 'higher', 'nearest', 'midpoint', 'linear']: q1 = np.percentile(x, 25, method=m) q3 = np.percentile(x, 75, method=m) print(f'method={m}: Q1={q1:.0f}, Q3={q3:.0f}, IQR={q3-q1:.0f}') |
分位点の定義・補間方法、 箱ひげ図、 外れ値検出を一気に把握。
47 都道府県の平均所得分布を四分位で記述し、 外れ値(Tukey フェンス)を抽出します。
| 指標 | SSDSE-B 概算値 | 解釈 |
|---|---|---|
| Q1(25%点) | ≈ 290 万円 | 下位 1/4 県の境 |
| Q2(中央値) | ≈ 310 万円 | 中央 24 番目の県 |
| Q3(75%点) | ≈ 340 万円 | 上位 1/4 県の境 |
| IQR | ≈ 50 万円 | 中央 50% の幅 |
| 下フェンス Q1−1.5·IQR | ≈ 215 万円 | これ以下は外れ値候補 |
| 上フェンス Q3+1.5·IQR | ≈ 415 万円 | 東京などが該当しがち |
1 2 3 4 5 6 7 8 9 10 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) x = df['平均所得'].dropna() q1, q2, q3 = x.quantile([0.25, 0.5, 0.75]) iqr = q3 - q1 low, high = q1 - 1.5 * iqr, q3 + 1.5 * iqr print(f'Q1={q1:.0f}, Q2={q2:.0f}, Q3={q3:.0f}, IQR={iqr:.0f}') print(f'下フェンス={low:.0f}, 上フェンス={high:.0f}') outliers = df.loc[(x < low) | (x > high), ['都道府県','平均所得']] print('外れ値県:'); print(outliers) |
method='linear' など使用法を明示すること。 numpy 1.22 以降は numpy.quantile(method=...) で 9 種類全部選べます。whis= 引数で挙動を制御できます。numpy.quantile を使うと不偏推定になりません。 加重分位点(weighted quantile)が必要。 statsmodels.stats.weightstats.DescrStatsW や numpy.percentile は加重対応不十分なので、 pandas.Series.quantile(weights=...) (pandas 2.x 以降)または自前で計算する必要があります。1 2 3 4 5 | import numpy as np x = df['平均所得'].dropna().values for m in ['linear', 'lower', 'higher', 'midpoint', 'nearest']: q1, q3 = np.quantile(x, [0.25, 0.75], method=m) print(f'{m}: Q1={q1:.1f}, Q3={q3:.1f}, IQR={q3-q1:.1f}') |
1 2 3 | import pandas as pd print(df['平均所得'].describe()) # min/Q1/Q2/Q3/max print(df['平均所得'].quantile([0.1, 0.25, 0.5, 0.75, 0.9])) |
1 2 3 | from scipy import stats print('IQR:', stats.iqr(x)) print('IQR(rng=10-90):', stats.iqr(x, rng=(10, 90))) # 範囲を変えられる |
1 2 3 4 5 6 7 | import numpy as np median = np.median(x) mad = np.median(np.abs(x - median)) # MAD ベースの外れ値判定(Hampel 法、 |z| > 3.5 を外れ値) modified_z = 0.6745 * (x - median) / mad outlier_idx = np.where(np.abs(modified_z) > 3.5)[0] print('外れ値インデックス:', outlier_idx) |
| 指標 | 特徴 | 外れ値感度 |
|---|---|---|
| 分散・SD | 数値計算が易 | 高(弱い) |
| IQR (Q3−Q1) | 中央50% | 低(強い) |
| MAD | 中央値からの中央絶対偏差 | 最低(最強) |
| 範囲(max−min) | 直感的 | 最高(最弱) |
| 十分位範囲(P90−P10) | 中央 80% | 中 |
| 変動係数 CV | 単位なしの相対指標 | 高 |