論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
分位点回帰
Quantile Regression
通常の回帰が「平均」を予測するのに対し、 分位点回帰は 「中央値・上位 10%・下位 10%」など任意の分位点 を予測する手法。
外れ値に強く、 予測区間(不確実性) を自然に出せる。
回帰 頑健統計 予測区間 分位点

🔖 キーワード索引(追補)

🎨 直感で掴む 📐 ピンボール損失 🧮 実値で計算 🐍 statsmodels 🐍 GBR (sklearn) 🐍 損失検証 🐍 予測区間 ⚠️ 落とし穴 🌐 関連手法 📚 グループ教材

💡 30 秒で分かる結論(追補)

📍 文脈ボックス:あなたが今見ているもの

この記事は 回帰分析 の「OLS の発展系」群に属し、 OLS / ロバスト回帰 と並列、 機械学習基礎 の予測区間構築や、 リスク分析(VaR) の数理基盤として参照される。 SSDSE-B-2026 を題材に「平均だけ見ていたら気づけない上位 / 下位 25% の傾向」を体感する。

🎨 直感で掴む:「平均回帰」と「分位点回帰」の違い

身長と体重の散布図を想像してほしい。 OLS は「身長 x cm の人の体重の平均」を予測する直線を引く。 分位点回帰 (τ=0.9) は「身長 x cm の人の体重の上位 10% ライン」を引く。 同じデータでも見える線が違う。 SSDSE-B-2026 の「人口 → 出生数」でも同様で、 平均で見ると傾き 0.0061 だが、 上位 25%(τ=0.75)の県では 0.0063 と若干急、 下位 25%(τ=0.25)では 0.0059 と緩い。 大県ほど「出生数のばらつきが平均からポジティブ側に広い」傾向が読み取れる。

手法捉えるもの外れ値耐性出力
OLS (普通の回帰)条件付き平均 $E[Y \mid X]$弱い(外れ値 1 個で歪む)1 本の点予測
中央値回帰 (τ=0.5)条件付き中央値 $\text{Med}[Y \mid X]$強い (LAD と等価)1 本の点予測 (頑健)
分位点回帰 (τ=0.05, 0.95)下側 5%・上側 5% 分位点構造的に頑健90% 予測区間
分位点回帰 (全 τ)条件付き分布全体強い分布関数の推定

📐 ピンボール損失(分位点回帰のコア)

分位点回帰の損失関数 $\rho_\tau(u)$ は「ピンボール(pinball)損失」と呼ばれる非対称な絶対値損失:

$$ \rho_\tau(u) = u \cdot (\tau - \mathbb{1}_{u < 0}) = \begin{cases} \tau \cdot u & \text{if } u \ge 0 \\ (\tau - 1) \cdot u & \text{if } u < 0 \end{cases} $$

$$ \hat{\beta}(\tau) = \arg\min_{\beta} \sum_{i=1}^{n} \rho_\tau(y_i - x_i^\top \beta) $$

🔬 数式を言葉で読み解く

🧮 実値で計算してみる:SSDSE-B-2026 で 25/50/75 分位回帰

SSDSE-B-2026 (2023 年, 47 県) で、 説明変数 = 人口 (A1101)、 被説明変数 = 出生数 (A4101) として、 OLS と 3 種類の分位回帰を比較する。

手法傾き (slope)切片 (intercept)解釈
OLS (平均)0.006104−676.9人口 100 万増 → 出生 6100 増(平均)
τ=0.250.005885−1037.3下位 25% ライン: やや傾き低い
τ=0.50 (中央値)0.005748−287.5中央値: OLS より頑健、 傾き微減
τ=0.750.0063100.00上位 25%: 傾き急、 大県ほど出生数増
--- ピンボール損失の比較 (τ=0.5) --- 中央値 (Med=9524) を予測値とした場合: ρ_0.5 = 4856.84 平均 (Mean=15474) を予測値とした場合: ρ_0.5 = 5919.73 → 中央値の方がピンボール損失が小さい (4857 < 5920)。 これは「τ=0.5 の最適解は中央値」という数学的事実を実値で確認したもの。

🐍 Python 実装

① statsmodels で 3 種類の分位回帰

🎯 このコードでやること: SSDSE-B-2026 の (人口, 出生数) で τ ∈ {0.25, 0.5, 0.75} の分位回帰を statsmodels.formula.api.quantreg で実行し、 OLS と並べて比較。

📥 入力データ (SSDSE-B-2026 2023 年 抜粋):

Code Prefecture A1101 (人口) A4101 (出生数) 0 R01000 北海道 5,092,000 24,430 144 R13000 東京都 14,086,000 86,348 312 R27000 大阪府 8,763,000 55,292 552 R47000 沖縄県 1,468,000 12,549 ※ index は元 CSV の行番号(県ごとに 12 年分が連続するため 2023 年行は 12 行おき)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import statsmodels.formula.api as smf

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023][['A1101','A4101']]
d.columns = ['pop','birth']

# OLS と分位回帰を 3 つ
ols = smf.ols('birth ~ pop', d).fit()
print(f'OLS: slope={ols.params["pop"]:.6f}, intercept={ols.params["Intercept"]:.2f}')

for tau in [0.25, 0.5, 0.75]:
    qr = smf.quantreg('birth ~ pop', d).fit(q=tau)
    print(f'τ={tau}: slope={qr.params["pop"]:.6f}, '
          f'intercept={qr.params["Intercept"]:.2f}')

📤 実行すると次の出力が得られる:

OLS: slope=0.006104, intercept=-676.90 τ=0.25: slope=0.005885, intercept=-1037.29 τ=0.5: slope=0.005748, intercept=-287.53 τ=0.75: slope=0.006310, intercept=0.00

💬 結果の読み方: τ が増えるほど傾きがおおむね急になる(0.0059 → 0.0063)。 これは「人口の大きい県では出生数の 上限が傾向以上に伸びる」ことを意味する(東京・神奈川の集中効果)。 OLS の傾き 0.0061 はちょうど中央値と平均値の間に来ている。

② sklearn の GradientBoostingRegressor で非線形分位回帰

🎯 このコードでやること: GBR の loss='quantile' オプションで τ=0.1, 0.5, 0.9 の非線形分位回帰を実行。 線形より柔軟に「分布の端」を捉えられる。

📥 入力データ: SSDSE-B-2026 (47 県 × 2023), X=人口、 y=出生数。

 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
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023]
X = d[['A1101']].values
y = d['A4101'].values

# 3 つの τ で別々にモデルを学習
models = {}
for tau in [0.1, 0.5, 0.9]:
    gbr = GradientBoostingRegressor(
        loss='quantile', alpha=tau,
        n_estimators=100, max_depth=3, random_state=42
    )
    gbr.fit(X, y)
    models[tau] = gbr

# 東京 (人口 14086000) の予測区間
x_tokyo = np.array([[14086000]])
for tau, m in models.items():
    print(f'東京 (人口 14086000) τ={tau}: 予測出生数 = {m.predict(x_tokyo)[0]:,.0f}')

# 平均的県 (人口 200 万) の予測区間
x_avg = np.array([[2000000]])
for tau, m in models.items():
    print(f'仮想県 (人口 2000000) τ={tau}: 予測出生数 = {m.predict(x_avg)[0]:,.0f}')

📤 実行すると次の出力が得られる:

東京 (人口 14086000) τ=0.1: 予測出生数 = 10,212 東京 (人口 14086000) τ=0.5: 予測出生数 = 86,239 東京 (人口 14086000) τ=0.9: 予測出生数 = 86,347 仮想県 (人口 2000000) τ=0.1: 予測出生数 = 10,212 仮想県 (人口 2000000) τ=0.5: 予測出生数 = 11,015 仮想県 (人口 2000000) τ=0.9: 予測出生数 = 12,508

💬 結果の読み方: 東京は人口が突出しており、 訓練データ内で「東京サイズの県は 1 件のみ」のため、 τ=0.5 と τ=0.9 がほぼ同じ値(86,239 と 86,347)になる(過学習)。 仮想県 (人口 200 万) では「下側 10K 〜 上側 12.5K」の予測区間 (約 2300 幅) が得られ、 OLS の単一点予測より「不確実性」を表現できている。 なお τ=0.1 は木モデルの外挿制約で東京・仮想県とも同じ低値 (10,212) を返す点に注意。

③ ピンボール損失を自前で計算 → τ ごとの最小化を確認

🎯 このコードでやること: 「定数予測 c」を様々な値で試し、 ピンボール損失を計算する。 τ=0.5 では中央値、 τ=0.25 では下位 25% 分位点、 τ=0.75 では上位 25% 分位点で損失が最小になることを実値で確認。

📥 入力データ: SSDSE-B-2026 47 県の出生数 A4101 (平均 15,474、 中央値 9,524、 25%=5,472、 75%=14,390)。

 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
import pandas as pd
import numpy as np

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
y = df[df['SSDSE-B-2026']==2023]['A4101'].values  # 出生数

def pinball(y, c, tau):
    u = y - c
    return np.where(u >= 0, tau * u, (tau - 1) * u).mean()

# 候補値: 25%, 中央値, 75%, 平均
candidates = {
    'q25 (5472)': 5472,
    'median (9524)': 9524,
    'q75 (14390)': 14390,
    'mean (15474)': 15474,
}

print('--- τ=0.25 (q25 が最小であるはず) ---')
for name, c in candidates.items():
    print(f'  c={name}: ρ_0.25 = {pinball(y, c, 0.25):.2f}')

print('--- τ=0.50 (中央値が最小であるはず) ---')
for name, c in candidates.items():
    print(f'  c={name}: ρ_0.50 = {pinball(y, c, 0.50):.2f}')

print('--- τ=0.75 (q75 が最小であるはず) ---')
for name, c in candidates.items():
    print(f'  c={name}: ρ_0.75 = {pinball(y, c, 0.75):.2f}')

📤 実行すると次の出力が得られる:

--- τ=0.25 (q25 が最小であるはず) --- c=q25 (5472): ρ_0.25 = 2772.69 ← 最小 c=median (9524): ρ_0.25 = 3369.39 c=q75 (14390): ρ_0.25 = 5371.29 c=mean (15474): ρ_0.25 = 5919.78 --- τ=0.50 (中央値が最小であるはず) --- c=q25 (5472): ρ_0.50 = 5273.14 c=median (9524): ρ_0.50 = 4856.84 ← 最小 c=q75 (14390): ρ_0.50 = 5642.24 c=mean (15474): ρ_0.50 = 5919.73 --- τ=0.75 (q75 が最小であるはず) --- c=q25 (5472): ρ_0.75 = 7773.59 c=median (9524): ρ_0.75 = 6344.29 c=q75 (14390): ρ_0.75 = 5913.20 ← 最小 c=mean (15474): ρ_0.75 = 5919.69

💬 結果の読み方: τ=0.25 のピンボール損失は q25=5472 で最小 (2773)、 τ=0.5 では median=9524 で最小 (4857)、 τ=0.75 では q75=14390 で最小 (5913)。 これが「ピンボール損失最小化 = 分位点推定」の証明的確認。 SSDSE は右裾が長いので、 平均 15474 は中央値 9524 から大きく乖離している。

④ 90% 予測区間の構築(τ=0.05, 0.95)

🎯 このコードでやること: τ=0.05 と τ=0.95 の分位回帰を訓練し、 「90% 予測区間 (PI)」を構築。 OLS の正規分布仮定に依存しない、 ノンパラメトリックな PI が得られる。

📥 入力データ: SSDSE-B-2026 47 県、 X=人口 A1101、 y=出生数 A4101。

 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
import pandas as pd
import numpy as np
from sklearn.ensemble import GradientBoostingRegressor

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023]
X = d[['A1101']].values
y = d['A4101'].values

def fit_qr(tau):
    m = GradientBoostingRegressor(
        loss='quantile', alpha=tau,
        n_estimators=200, max_depth=2, random_state=0
    )
    m.fit(X, y)
    return m

m_lo = fit_qr(0.05)
m_md = fit_qr(0.50)
m_hi = fit_qr(0.95)

# 仮想県 (人口 100 万, 300 万, 1000 万) で予測区間
for pop in [1_000_000, 3_000_000, 10_000_000]:
    xq = np.array([[pop]])
    lo, md, hi = m_lo.predict(xq)[0], m_md.predict(xq)[0], m_hi.predict(xq)[0]
    print(f'人口 {pop:>10,}: 90% PI = [{lo:,.0f}, {hi:,.0f}], 中央値={md:,.0f}')

# カバレッジ確認 (訓練データの何割が 90% PI に入るか)
lo_pred = m_lo.predict(X)
hi_pred = m_hi.predict(X)
covered = ((y >= lo_pred) & (y <= hi_pred)).mean()
print(f'\n訓練データのカバレッジ: {covered*100:.1f}% (目標 90%)')

📤 実行すると次の出力が得られる:

人口 1,000,000: 90% PI = [4,974, 6,757], 中央値=5,448 人口 3,000,000: 90% PI = [9,950, 16,711], 中央値=14,897 人口 10,000,000: 90% PI = [9,950, 71,762], 中央値=54,044 訓練データのカバレッジ: 83.0% (目標 90%)

💬 結果の読み方: 訓練データの 83.0% が 90% 予測区間に入る → 目標をやや下回るが N=47 の小サンプルでは妥当な範囲。 人口が増えるほど PI の絶対幅は広がる(約 1,800 → 6,800 → 62,000)。 なお τ=0.05 の下限は木モデルの外挿制約で人口 300 万以上では 9,950 に張り付く点に注意。 OLS で同じことをやろうとすると「残差の正規性」を仮定する必要があり、 出生数のような右裾分布では PI が不正確になりがち。 分位点回帰は仮定なしで PI を直接学ぶ。

⚠️ 落とし穴(追補 6 件)

  1. 分位点のクロス問題: τ=0.25 の予測が τ=0.5 の予測より大きくなることがある(quantile crossing)。 単調性を保つには cqr ライブラリや QuantileForest を使う。
  2. 信頼区間の難しさ: 分位点回帰の係数の標準誤差はブートストラップが基本。 解析解は存在するが、 誤差分布の独立性仮定に依存。 サンプルが小さい時 (n < 100) は注意。
  3. サンプルサイズと裾の精度: τ=0.01 や τ=0.99 の極端な分位点は、 サンプル数の少ない裾を推定するため誤差が大きい。 SSDSE 47 件で τ=0.05 の予測は信頼性が低い。
  4. 離散変数の問題: y が離散値(出生数の整数値)の場合、 分位点が「同じ値」になることがある(plateau 現象)。 線形補間 (interpolation) で対処。
  5. 解釈の罠: 「τ=0.75 の傾きが大きい → 高人口県では出生数の上限が伸びる」と言いたくなるが、 これは「上位 25% の 条件付き分位点」であり「上位 25% の県」ではない。 慎重に言葉を選ぶ必要がある。
  6. 「外れ値除去と同じ」という誤解: 分位点回帰は外れ値除去しない。 全データを使ったまま、 ピンボール損失が残差の「符号」だけを見る(大きさを二乗しない)ため外れ値 1 点の 影響が有界になる、 というロバスト統計的な仕組み。 外れ値を先に削除(トリミング)すると、 分布の裾情報が失われ τ=0.9 や τ=0.1 の推定そのものが壊れる。 「裾を 捨てる」のではなく「裾を 推定対象にする」のが分位点回帰である。
領域手法関係
並列概念OLS / ロバスト回帰条件付き分布の別側面
特殊例中央値回帰 (τ=0.5) = LADL1 損失最小化と等価
機械学習版Quantile Random Forest / LightGBM-quantile / NGBoost非線形・高次元対応
応用予測区間 / VaR / Conformal Prediction不確実性定量化の標準

🔗 関連用語(前提・並列・発展)

前提: 分位点 / OLS / 線形計画 / 損失関数
並列: ロバスト回帰 / GAM / 中央値回帰
発展: 分位点 RF / LightGBM / NGBoost / Conformal Prediction

📚 関連グループ教材

📐 補足: 線形計画問題としての分位点回帰

分位点回帰の最適化は線形計画(LP)として定式化できる。 補助変数 $u_i^+, u_i^- \ge 0$ を導入:

$$ \min_{\beta, u^+, u^-} \sum_{i=1}^{n} \left[\tau u_i^+ + (1-\tau) u_i^-\right] $$

$$ \text{s.t.}\quad y_i - x_i^\top \beta = u_i^+ - u_i^-,\ \ u_i^+, u_i^- \ge 0 $$

シンプレックス法や内点法で解ける。 statsmodels の quantreg は内部で内点法(IRLS の派生)を呼んでいる。 n=10,000、 p=50 程度なら 1 秒以内、 n=100,000 でも数秒で解ける。

🐍 Python 実装 (補足): scipy.optimize で線形計画解

🎯 このコードでやること: 上記の LP 定式化を scipy.optimize.linprog で自前で解き、 statsmodels の結果と一致するか確認。

📥 入力データ: SSDSE-B-2026 47 県、 X=人口、 y=出生数 (上記と同じ)。

 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
import pandas as pd
import numpy as np
from scipy.optimize import linprog

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023]
y = d['A4101'].values.astype(float)
X = np.column_stack([np.ones(len(d)), d['A1101'].values.astype(float)/1e4])  # [1, pop万人](well-conditioned)

n, p = X.shape
tau = 0.5  # 中央値回帰

# 変数: [beta (p個), u+ (n個), u- (n個)]
# 目的: minimize sum(tau * u+ + (1-tau) * u-)
c = np.concatenate([np.zeros(p), tau * np.ones(n), (1-tau) * np.ones(n)])

# 制約: X @ beta + u+ - u- = y → [X, I, -I] @ [beta; u+; u-] = y
A_eq = np.hstack([X, np.eye(n), -np.eye(n)])
b_eq = y

# beta は無制約、 u+, u- >= 0
bounds = [(None, None)] * p + [(0, None)] * (2 * n)

res = linprog(c, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs')
beta = res.x[:p]
print(f'LP解 (τ=0.5): intercept={beta[0]:.2f}, slope={beta[1]:.6f}')
print(f'statsmodels比: intercept=-287.53, slope=57.484981 (IRLS 未収束のため LP 解とずれる)')

📤 実行すると次の出力が得られる:

LP解 (τ=0.5): intercept=-711.86, slope=61.805948 statsmodels比: intercept=-287.53, slope=57.484981 (IRLS 未収束のため LP 解とずれる)

💬 結果の読み方: 自前 LP(HiGHS)は厳密解を返す。 一方 statsmodels の QuantReg は IRLS 反復が上限(max_iter=1000)に達して未収束のまま止まるため、 この n=47 データでは両者が僅かにずれる(ピンボール損失合計: LP 26,872.5 < statsmodels 28,551.0 で LP 解の方が厳密に良い)。 分位点回帰の「本体」は線形計画 1 個に集約されることが確認できる。 OLS が正規方程式 $(X^\top X)^{-1} X^\top y$ の閉形式解を持つのに対し、 分位点回帰は LP の反復解法が必要で計算量は増えるが、 現代の HIGHS / Mosek なら大規模問題でも実用速度。

📐 補足: 「数式を言葉で読み解く」追加版

分位点回帰の条件付き分位点関数 $Q_Y(\tau \mid X = x) = x^\top \beta(\tau)$ の意味を、 もう一段噛み砕く:

🌐 補足: 業界別ユースケース

業界τ の選び方用途
金融 (VaR)τ=0.01, 0.05最大損失(テイルリスク)の推定
医療 (BMI 成長曲線)τ=0.03, 0.5, 0.97小児の成長カーブ標準
公衆衛生 (出生体重)τ=0.1, 0.5, 0.9低出生体重リスク予測
電力 (負荷予測)τ=0.95ピーク需要への備え
物流 (配送時間)τ=0.9「90% の確率で X 分以内」保証
教育 (試験成績)τ=0.1, 0.5, 0.9下位層・中位層・上位層の傾向
不動産 (家賃推定)τ=0.25, 0.75「相場上下幅」を示す

📚 確認チェックリスト

📐 補足: 分位点回帰 vs Conformal Prediction

両者とも「予測区間」を作る手法だが、 アプローチが全く異なる:

観点分位点回帰Conformal Prediction
対象条件付き分位点周辺カバレッジ
仮定線形・GBR 等のモデル交換可能性のみ
保証無限サンプル極限有限サンプルで厳密
適応性X に応じて区間幅が変わる基本版は固定幅
ハイブリッドCQR (Conformalized Quantile Regression)両者の長所を統合

実務では「分位点回帰で適応的な区間を作り、 Conformal で有限サンプル保証を付与する」CQR が主流になりつつある (Romano et al., 2019)。

📚 さらに読むには

🧮 実値で計算 (補足): SSDSE 全 12 年で τ-process プロット相当

SSDSE-B-2026 を 2023〜2012 年(12 年分)に拡張し、 各年で 47 県の (人口, 出生数) に τ=0.1, 0.25, 0.5, 0.75, 0.9 の分位回帰を当てる。 傾き $\beta_1(\tau)$ の年次推移を表で示す。

τ=0.1τ=0.25τ=0.5τ=0.75τ=0.9OLS
20230.005690.005890.005750.006310.006470.00610
20220.005740.005950.006160.006530.006820.00639
20210.005790.006230.006250.006810.007170.00668
20200.006190.006440.006650.007090.007370.00694
20150.007560.007720.007990.008380.008770.00821
20120.008000.007880.008150.008320.009000.00826

観察 1: 年が進むにつれて傾き全体が低下 (0.0083 → 0.0061) → 少子化進行で「人口あたり出生数」が全国的に下がっている。
観察 2: 各年内で τ=0.9 / τ=0.1 比 = 約 1.13 倍 → 高分位(高人口県)の方が傾きが急という構造は不変。
観察 3: OLS の傾きはおおむね τ=0.5〜0.75 の間 → 出生数分布の右裾の影響を平均が引っ張られている。

📐 補足: 多変量分位点回帰

複数の説明変数を含めることも可能。 SSDSE-B-2026 で X = {人口 A1101, 年平均気温 B4101, 婚姻件数 A9101} を使い y = 出生数を推定する例:

$$ Q_Y(\tau \mid X) = \beta_0(\tau) + \beta_1(\tau) \cdot \text{pop} + \beta_2(\tau) \cdot \text{temp} + \beta_3(\tau) \cdot \text{marr} $$

係数 $\beta_j(\tau)$ は τ ごとに変わるので、 「婚姻件数が多い県では中央値 (τ=0.5) の出生数は増えるが、 上位 (τ=0.9) では影響が変わる」といった分布依存の関係を抽出できる。 OLS では平均 1 本しか得られないので、 こうした構造は見落とされる。

⚠️ 落とし穴 (補足 5 件)

  1. τ の選択ミス: 「とりあえず 0.25, 0.5, 0.75」では業務目的に合わないことが多い。 VaR は τ=0.01, SLA は τ=0.99 など、 「何を保証したいか」から逆算する。
  2. カバレッジの誤読: 「90% PI を作ったから 90% 当たる」は周辺的にしか保証されない。 X の特定領域(外挿)ではカバレッジが落ちる。 必ず検証データで 条件付きカバレッジを確認する。
  3. τ=0.5 を OLS の代替に使う罠: 中央値回帰は確かに外れ値に強いが、 「平均」を直接知りたい時には誤った結論を導く。 例: 「平均収入」を中央値で報告すると過小評価。
  4. 非線形関係の見落とし: 線形分位点回帰は直線しか引けない。 SSDSE のように「東京が突出した非線形性」がある場合、 線形 QR は東京で予測が外れる。 GBR-quantile か Quantile RF を使う。
  5. 解釈不可能になりがち: τ ごとに係数が変わるので「人口の影響」を一文で説明できない。 QR は「分布のどこに効くか」を分解する手法であり、 単一の effect size を返さない。 これは欠点ではなく特徴。

🐍 Python 実装 (補足 2): Quantile Crossing の検出と修正

🎯 このコードでやること: SSDSE-B-2026 で τ=0.1, 0.5, 0.9 を別々に推定し、 「クロスしている点」を検出。 単調性を保つ後処理(sorting)を適用。

📥 入力データ: SSDSE-B-2026 47 県、 X=人口、 y=出生数。

 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
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023][['A1101','A4101']].copy()
d.columns = ['pop','birth']

# 3 つの τ で線形分位回帰
preds = {}
for tau in [0.1, 0.5, 0.9]:
    qr = smf.quantreg('birth ~ pop', d).fit(q=tau)
    preds[tau] = qr.predict(d)

pred_df = pd.DataFrame(preds)
print('--- 推定結果 (最初の 5 行) ---')
print(pred_df.head())

# クロス検出: τ=0.1 > τ=0.5 や τ=0.5 > τ=0.9 となる行
cross = ((pred_df[0.1] > pred_df[0.5]) | (pred_df[0.5] > pred_df[0.9])).sum()
print(f'\nクロスしている件数: {cross} / {len(d)}')

# 修正: 各行で値をソート
fixed = pred_df.apply(lambda row: pd.Series(sorted(row.values),
                                            index=[0.1, 0.5, 0.9]), axis=1)
print('\n--- ソート後 (クロス解消) ---')
print(fixed.head())

📤 実行すると次の出力が得られる:

--- 推定結果 (最初の 5 行) --- 0.1 0.5 0.9 0 27,807 28,984 32,963 12 5,552 6,519 7,665 24 5,432 6,398 7,529 ... クロスしている件数: 0 / 47 --- ソート後 (クロス解消) --- 0.1 0.5 0.9 0 27,807 28,984 32,963 ...

💬 結果の読み方: SSDSE のような直線関係が強いデータではクロスはほぼ起きない(0 件)。 一方、 非線形なデータ(医療コスト等)ではクロスが頻発し、 sorting 修正が必須。 より洗練された手法は Chernozhukov et al. (2010) の「rearrangement」で、 連続関数として単調化する。

📐 補足: 推定量の漸近分布

Koenker (2005) によれば、 分位点回帰推定量 $\hat{\beta}(\tau)$ は次の漸近分布を持つ:

$$ \sqrt{n}(\hat{\beta}(\tau) - \beta(\tau)) \xrightarrow{d} N\left(0,\ \tau(1-\tau) \cdot D^{-1} \cdot J \cdot D^{-1}\right) $$

ここで $x_i$ は説明変数ベクトル(計画行列 $X$ の第 $i$ 行)で、 $D = E\left[f_{Y \mid X}\bigl(Q_\tau(Y \mid X) \mid X\bigr) \, x_i x_i^\top\right]$、 $J = E[x_i x_i^\top]$。 $D$ の推定には条件付き密度 $f_{Y \mid X}$ が必要で、 これがブートストラップが好まれる理由でもある。

🐍 Python 実装 (補足 3): ブートストラップで信頼区間

🎯 このコードでやること: τ=0.5 の傾き係数の 95% 信頼区間を、 1000 回のブートストラップで推定。 statsmodels が返すデフォルトの「IID 漸近 SE」と比較。

📥 入力データ: SSDSE-B-2026 47 県、 (人口, 出生数)。

 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
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023][['A1101','A4101']].copy()
d.columns = ['pop','birth']

# ブートストラップ 1000 回
B = 1000
slopes = []
for b in range(B):
    sample = d.sample(n=len(d), replace=True, random_state=b)
    qr = smf.quantreg('birth ~ pop', sample).fit(q=0.5)
    slopes.append(qr.params['pop'])
slopes = np.array(slopes)

# 95% 信頼区間
ci_lo, ci_hi = np.percentile(slopes, [2.5, 97.5])
print(f'ブートストラップ平均: {slopes.mean():.6f}')
print(f'ブートストラップ SE: {slopes.std():.6f}')
print(f'95% CI (bootstrap): [{ci_lo:.6f}, {ci_hi:.6f}]')

# statsmodels の漸近 SE と比較
qr_full = smf.quantreg('birth ~ pop', d).fit(q=0.5)
print(f'\nstatsmodels: slope={qr_full.params["pop"]:.6f}, '
      f'SE={qr_full.bse["pop"]:.6f}')
print(f'statsmodels 95% CI: [{qr_full.conf_int().loc["pop", 0]:.6f}, '
      f'{qr_full.conf_int().loc["pop", 1]:.6f}]')

📤 実行すると次の出力が得られる:

ブートストラップ平均: 0.005933 ブートストラップ SE: 0.000218 95% CI (bootstrap): [0.005474, 0.006283] statsmodels: slope=0.005748, SE=0.000085 statsmodels 95% CI: [0.005577, 0.005920]

💬 結果の読み方: ブートストラップ CI [0.00547, 0.00628] は statsmodels の漸近 CI [0.00558, 0.00592] より明らかに広い。 これは「右裾の長い分布」「N=47 の小サンプル」で漸近近似が楽観的になっているため。 一般に分位点回帰では ブートストラップ CI を信用すべき。

🗺 概念マップ (補足): 損失関数の系譜

損失対応する推定量対応する分布の点
$L_2$ (二乗)OLS平均
$L_1$ (絶対値)LAD = 中央値回帰中央値
HuberHuber 回帰頑健な平均
ピンボール $\rho_\tau$分位点回帰τ 分位点
$\epsilon$-insensitiveSVRマージン中央
CRPS確率予測分布全体

🌐 補足: 分位点回帰の歴史

🐍 Python 実装 (補足 4): LightGBM の quantile regression

🎯 このコードでやること: LightGBM の objective='quantile' で SSDSE-B-2026 の多変量分位回帰。 説明変数 3 つ(人口・気温・婚姻件数)で出生数の τ=0.1, 0.5, 0.9 を推定。

📥 入力データ: SSDSE-B-2026 47 県、 X=[A1101, B4101, A9101]、 y=A4101。

 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
import pandas as pd
import numpy as np
import lightgbm as lgb

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[df['SSDSE-B-2026']==2023].dropna(subset=['A1101','B4101','A9101','A4101'])
X = d[['A1101', 'B4101', 'A9101']].values
y = d['A4101'].values

# 3 つの τ で別々に学習
preds = {}
for tau in [0.1, 0.5, 0.9]:
    model = lgb.LGBMRegressor(
        objective='quantile', alpha=tau,
        n_estimators=100, max_depth=3,
        learning_rate=0.05, random_state=42, verbose=-1
    )
    model.fit(X, y)
    preds[tau] = model.predict(X)

# 訓練データ上のカバレッジ (90% PI = τ=0.1〜0.9)
covered = ((y >= preds[0.1]) & (y <= preds[0.9])).mean()
print(f'90% PI カバレッジ (訓練): {covered*100:.1f}%')

# 仮想入力(人口 500 万、 気温 15度、 婚姻件数 2 万件)の予測区間
x_new = np.array([[5_000_000, 15.0, 20000]])
for tau in [0.1, 0.5, 0.9]:
    model = lgb.LGBMRegressor(
        objective='quantile', alpha=tau,
        n_estimators=100, max_depth=3,
        learning_rate=0.05, random_state=42, verbose=-1
    )
    model.fit(X, y)
    print(f'τ={tau}: 予測 = {model.predict(x_new)[0]:,.0f}')

📤 実行すると次の出力が得られる:

90% PI カバレッジ (訓練): 80.9% τ=0.1: 予測 = 9,963 τ=0.5: 予測 = 15,000 τ=0.9: 予測 = 52,199

💬 結果の読み方: LightGBM-quantile は非線形・多変量に対応し、 訓練データではカバレッジ 80.9% と名目 80%(τ=0.1〜0.9)にほぼ一致。 実務では検証データでチューニング (alpha, n_estimators) が必要。 仮想入力の PI 幅 [10K, 52K] は OLS では得られない不確実性表現で、 「人口だけ」より精度が上がる。

📐 補足: Expectile 回帰との対比

Expectile(期待分位点)は 1987 年に Newey & Powell が提案した別の非対称損失。 ピンボール損失の代わりに「非対称二乗損失」を使う:

$$ \rho^E_\tau(u) = u^2 \cdot |\tau - \mathbb{1}_{u<0}| = \begin{cases} \tau u^2 & \text{if } u \ge 0 \\ (1-\tau) u^2 & \text{if } u < 0 \end{cases} $$

観点分位点 (Quantile)期待分位点 (Expectile)
損失ピンボール (L1)非対称 L2
頑健性高い低い (L2 由来)
LP 必要IRLS で解析的
直感的解釈τ% 以下に何点非対称 MSE 最小化点
金融応用VaRExpected Shortfall (ES)

Basel III では 2017 年から VaR から ES への移行が進んでおり、 Expectile 回帰の重要性が増している。

🌐 補足: モデル評価指標

分位点回帰の予測精度は「平均二乗誤差 (MSE)」では測れない(OLS 用)。 適切な指標は:

📐 補足: 分位点回帰のハイパーパラメータ早見表

パラメータ推奨範囲効果
τ (分位点)0.01〜0.99業務目的で決定(VaR=0.01, SLA=0.95 等)
n_estimators (GBR)100〜500多すぎると過学習、 early stopping 推奨
max_depth (GBR)2〜5深いほど非線形対応、 過学習リスク
learning_rate0.01〜0.1小さいほど安定、 n_estimators 増要
min_samples_leaf5〜20極端 τ では大きめに(裾の安定化)
bootstrap 回数500〜2000CI 推定用、 n 小さい時は多めに

🔬 数式を言葉で読み解く (補足): なぜピンボール損失で分位点が出るか

ピンボール損失の期待値 $E[\rho_\tau(Y - c)]$ を $c$ で微分してゼロと置くと:

$$ \frac{\partial}{\partial c} E[\rho_\tau(Y - c)] = -\tau \cdot P(Y \ge c) + (1-\tau) \cdot P(Y < c) = 0 $$

$$ \implies P(Y < c) = \tau \implies c = Q_\tau(Y) $$

つまり「ピンボール損失の期待値を最小化する $c$ は、 分布 $Y$ の τ 分位点に他ならない」が証明される。 これが分位点回帰の数学的核心。 同じロジックで:

「損失関数を決めれば、 分布のどの点を予測するかが決まる」。 これは予測モデリングの最も深い原理の 1 つ。

🧮 実値 (補足 2): 損失関数比較表

SSDSE-B-2026 47 県の出生数を「定数 c で予測する」場合、 c の値を変えながら異なる損失を比較:

cL2 (MSE)L1 (MAE)ρ_0.25ρ_0.5ρ_0.75ρ_0.9
3,000444M12,4743,1186,2379,35511,226
5,472 (q25)388M10,5462,773 ★5,2737,7749,274
9,524 (med)323M9,714 ★3,3694,857 ★6,3447,237
14,390 (q75)289M11,2845,3715,6425,913 ★6,076
15,474 (mean)288M ★11,8395,9205,9205,9205,920
40,000 (≈q90)890M28,19220,22714,0967,9644,285 ★

★ は各列で最小値。 L2 最小 = 平均、 L1 最小 = 中央値、 ρ_0.25 最小 = q25、 ρ_0.75 最小 = q75、 ρ_0.9 最小 = q90 付近 (c=40,000。 実測 q90=38,238)。 「損失と推定対象の対応」が一目で分かる。 SSDSE は右裾が長いので、 平均 (15K) と中央値 (9.5K) が大きく乖離している点に注意。

🐍 Python 実装 (補足 5): カバレッジ・キャリブレーション図

🎯 このコードでやること: τ ∈ {0.1, 0.2, ..., 0.9} の 9 つの分位回帰を学習し、 検証データ上で「予測されたカバレッジ」vs「目標カバレッジ」をテーブル化。 ideally 一致するはず。

📥 入力データ: SSDSE-B-2026 全 12 年 × 47 県 = 564 件、 X=人口、 y=出生数。 ホールドアウト 80/20。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = df[['A1101','A4101']].dropna()  # 全 12 年分をプールしテスト標本数を確保
d.columns = ['pop','birth']
train, test = train_test_split(d, test_size=0.2, random_state=42)

results = []
for tau in np.arange(0.1, 1.0, 0.1):
    qr = smf.quantreg('birth ~ pop', train).fit(q=tau)
    pred = qr.predict(test)
    coverage = (test['birth'] <= pred).mean()
    results.append({'target_τ': round(tau, 1),
                    'actual_coverage': round(coverage, 3),
                    'gap': round(coverage - tau, 3)})

print(pd.DataFrame(results).to_string(index=False))

📤 実行すると次の出力が得られる:

target_τ actual_coverage gap 0.1 0.115 0.015 0.2 0.177 -0.023 0.3 0.319 0.019 0.4 0.487 0.087 0.5 0.496 -0.004 0.6 0.584 -0.016 0.7 0.752 0.052 0.8 0.805 0.005 0.9 0.894 -0.006

💬 結果の読み方: 目標 τ と実測カバレッジが多くの点で ±0.02 前後(最大でも gap +0.087 @τ=0.4)に収まり、 分位回帰は おおむねキャリブレーション済み。 OLS + 正規分布仮定の PI は右裾の長いデータでカバレッジが落ちることがあるが、 分位回帰は分布仮定なしで近い水準を保つ。 これが「OLS の代替」として分位回帰が推される最大の理由。

📚 もう一段の検討事項

  1. τ を 0.01 間隔で 99 個推定する場合、 LP を 99 回解くのは時間がかかる。 → "Single-pass quantile regression" (Bondell et al., 2010) で同時推定可能。
  2. パネルデータ(県 × 年)では固定効果分位点回帰 (Koenker, 2004) が必要。 静的 QR では年内変動が捉えられない。
  3. 因果推論との接続: Conditional Quantile Treatment Effect (CQTE) で「処置が分布のどこに効くか」を推定可能。 RCT 不平等分析の標準。
  4. ベイズ版: ALD (Asymmetric Laplace Distribution) で尤度を構築するベイズ QR (Yu & Moyeed, 2001)。 事前分布で正則化が自然。
  5. 関数データ・縦断データへの拡張: Functional Quantile Regression、 縦断 QR で時系列・空間データ対応。

🌐 補足: 「OLS と QR をどう使い分けるか」フローチャート

問いYes ならNo なら
①「平均」を知りたいか?OLS②へ
② 外れ値が多いか?中央値回帰 (τ=0.5)③へ
③ 予測区間が必要か?QR (τ=0.05, 0.95)④へ
④ 分布全体の構造を知りたいか?複数 τ で QR⑤へ
⑤ 極端事象(VaR、 ピーク)が対象か?QR (τ=0.01 or 0.99)OLS で OK

📐 補足: 分位点回帰の正則化

高次元(説明変数が多い)QR では正則化が必要。 Belloni & Chernozhukov (2011) の L1-penalized QR:

$$ \hat{\beta}(\tau) = \arg\min_{\beta} \sum_{i=1}^{n} \rho_\tau(y_i - x_i^\top \beta) + \lambda \|\beta\|_1 $$

Lasso と同じく L1 罰則で sparse な係数を得る。 SCAD、 adaptive lasso も適用可能。 Python では statsmodels 単体では未サポートだが、 sklearn-quantilepyqreg で対応。

📚 さらに深く学ぶには (補足)

🎨 直感 (補足): 「弓道の的」の比喩

OLS は「的の中心 (黒丸) を狙う矢」と同じ。 ピンボール損失 (τ=0.5) は「的の中央線 (黒丸を通る左右対称線) を狙う」。 τ=0.9 は「的の上から 1 割の位置 (高得点ゾーン) を狙う」。 矢の散らばり方が同じでも、 狙う点が違えば「外れ方の罰則」が違う。 これがピンボール損失の非対称性の本質。

SSDSE で言えば、 「出生数の平均」は的の中央 (15K) だが、 中央値 (9.5K) は的の左寄り、 q75 (14K) は的の右寄り、 q90 (38K) は的の外周。 同じ 47 県データから 5 つの違う「狙い」を導ける。

📈 OLS と 5 つの τ-回帰を 47 都道府県で同時推定

分位点回帰の真価は、 OLS が描く 1 本の回帰線では捉えられない「条件付き分布の形状変化」を可視化できる点にあります。 SSDSE-B-2026 の 人口総数 を説明変数、 出生数 を目的変数として、 τ ∈ {0.10, 0.25, 0.50, 0.75, 0.90} の 5 本の回帰直線を引くと、 大都市圏 (人口総数が右側) で 傾きが拡大することが見えます。 つまり「人口あたり出生数」のばらつきは大都市ほど大きいという事実が、 1 本の OLS では完全に潰れて見えなくなります。

🐍 statsmodels.QuantReg で τ を 5 段階で推定

このコードでやること: SSDSE-B-2026 の data/raw/SSDSE-B-2026.csv から人口と出生数を抽出し、 statsmodels.regression.quantile_regression.QuantReg で 5 本の τ-回帰直線を一気に推定する。 同じ条件で OLS も並走させ、 傾き係数の差を比較する。

📥 入力データ (SSDSE-B-2026 の人口と出生数、 47 行):

SSDSE-2026 都道府県 人口総数 出生数 R01000 北海道 5092000 24430 R13000 東京都 14086000 86348 R47000 沖縄県 1468000 12549
1
2
3
4
5
6
7
8
9
🐍 quantreg_5taus.py<button onclick="copyTryitCode(this)">📋 コピー</button></div><div class="pygblock">import pandas as pd
import statsmodels.formula.api as smf
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].rename(columns={'A1101': 'pop', 'A4101': 'births'})
ols = smf.ols('births ~ pop', data=df).fit()
print(f'OLS         slope = {ols.params[1]:.5f}')
for q in [0.10, 0.25, 0.50, 0.75, 0.90]:
    qr = smf.quantreg('births ~ pop', data=df).fit(q=q)
    print(f'tau={q:.2f}    slope = {qr.params[1]:.5f}')</div>

📤 実行すると次の出力が得られる:

OLS slope = 0.006104 tau=0.10 slope = 0.005695 tau=0.25 slope = 0.005885 tau=0.50 slope = 0.005748 tau=0.75 slope = 0.006310 tau=0.90 slope = 0.006473

💬 τ が大きくなるほど傾きがおおむね増加(0.005695 → 0.006473、 約 1.14 倍)しています。 これは「人口の増加に伴い、 上位 10% の県では出生数の伸びが下位 10% の県よりやや急峻」であることを意味します。 OLS は中央付近 (τ=0.50 付近) の値しか返さず、 この 分布の歪みは永久に見えません。 政策決定では「τ=0.10 の県 (下位群)」をターゲットにすべきか「τ=0.90 の県 (上位群)」をターゲットにすべきかで対策が分かれるため、 分位点回帰が必須となります。

💡 30 秒で分かる結論

🍰 まずはやさしく

データの端っこまで予測する手法です。

平均だけでは見えないばらつきを知るために使います。

テストの点数が高い人と低い人で傾向が違うか調べます。

この章では分位点回帰の結論を短くまとめます。

📍 あなたが今見ているもの

🍰 まずはやさしく

分析のレポートでよく使われる手法です。

グループごとの動きの違いを比べるために使います。

人口が多い県と少ない県で宿泊客の増え方を比べます。

ここでは実際の分析でどう使われるかを見ます。

計量経済学や予測モデリングの論文で、 こんな表記を見たことがあるはずです:

本稿では 47 都道府県データに対し、 τ ∈ {0.10, 0.25, 0.50, 0.75, 0.90}分位点回帰 を適用した。
人口総数の係数は τ=0.10 で 0.31、 τ=0.90 で 0.58 と単調に増加し、 「規模の経済」が高生産地域で強い ことが示唆された。

この 分位点回帰 (Quantile Regression) は、 「人口が増えると平均的に延べ宿泊者数はどう動くか」ではなく、 「下位 10% の県(人口あたり宿泊者数が小さい県)」 と 「上位 10% の県(人口あたり宿泊者数が大きい県)」 で動き方がどう違うか を直接比較できる手法です。 1978 年に Koenker & Bassett が定式化して以来、 経済学・公衆衛生・気象予測・金融リスク管理など 「平均だけでは見えない構造」 を扱う分野で広く使われています。

🎨 直感で掴む — 「真ん中」 vs 「上限・下限」

🍰 まずはやさしく

データの「幅」を捉える虫眼鏡のようなものです。

最悪の場合や最高の場合を予測するために使います。

駅までの時間が一番かかったときは何分か考えます。

ここでは直感的に仕組みを理解するための例を読みます。

例え話:通勤時間の予測

あなたは「家から駅まで何分かかるか」を予測したいとします。 普通の回帰なら「平均 12 分」と返してきます。 でも、 本当に知りたいのは:

これらはすべて「同じデータから抽出できる別々の予測値」です。 分位点回帰は 3 本まとめて出してくれる 強力なツール。

不等分散(heteroskedastic)データに特に強い

所得が高いほど消費のばらつきも大きい」「人口の多い県ほど延べ宿泊者数のばらつきも大きい」 のような 不等分散 データでは、 通常の OLS が引いた 1 本の直線は 「真ん中だけ」 しか見せてくれません。

分位点回帰なら:
  • τ=0.10 の直線:「ほぼ下端を通る線」
  • τ=0.50 の直線:「中央値を通る線」
  • τ=0.90 の直線:「ほぼ上端を通る線」
3 本を引くと、 「データの広がりの形」 がそのまま見える。 線が 扇形に広がっていけば不等分散、 平行なら等分散。

OLS との対比

項目通常の回帰 (OLS)分位点回帰
予測対象条件付き平均 $E[Y \mid X]$条件付き分位点 $Q_\tau(Y \mid X)$
損失関数二乗誤差(外れ値に弱い)pinball loss(外れ値に強い)
仮定誤差の正規性・等分散性誤差分布の仮定不要
出力1 本の直線τ ごとに 1 本(複数引ける)
解釈「平均的にこう」「上位/中位/下位ではこう」
不等分散係数は変わらない係数が τ により変化(情報量大)

🎨 概念図で押さえる分位点回帰の本質(補遺)

分位点回帰の中核を 3 枚の図で再整理する。 平均回帰との違い、 ピンボール損失の非対称性、 τ ごとの回帰直線の束という 3 つの視点を並べる。

🖼 図 1: 最小二乗(平均)vs 分位点回帰(中央値・上下端)

最小二乗回帰は 条件付き平均 を引く線、 分位点回帰は 条件付き τ 分位点 を引く線である。 外れ値や非対称分布があると、 両者は系統的にずれる。

OLS と分位点回帰の比較

💬 読み方:赤の OLS 直線は上方の外れ値(赤丸)に引っ張られて中央値直線(青破線, τ=0.5)から離れる。 オレンジ(τ=0.9)と緑(τ=0.1)の 2 本は 予測区間の上下端 を直接与える。

🖼 図 2: ピンボール損失の非対称性

分位点回帰は ρ_τ(u) = u·(τ − 𝟙(u<0)) という非対称損失(ピンボール損失)を最小化する。 τ=0.5 のときは対称の絶対値損失、 τ≠0.5 のときは過小予測と過大予測の罰が異なる。

ピンボール損失関数

💬 読み方:青の τ=0.5 は左右対称の V 字(絶対値)。 オレンジ τ=0.9 は 右側(過小予測)の傾きが急 で、 つまり「予測 ŷ が真値 y より小さい状態」を強く罰する → 上方に寄った推定線になる。 緑 τ=0.1 はその逆。

🖼 図 3: τ を変えた予測区間バンド

τ を複数(例: 0.1, 0.5, 0.9)で並行推定すると、 条件付き予測区間が x の関数として自然に得られる。 平均回帰+等分散仮定の信頼区間と違い、 ヘテロスケダスティック(x で分散が変わる)な状況にも対応できる。

分位点回帰による予測区間

💬 読み方:水色の帯は τ=0.1〜0.9 の 80% 予測区間。 x が大きくなるにつれ帯が広がる場合、 条件付き分散の不均一性 を分位点回帰がそのまま捉えていることが分かる。 OLS の信頼区間(等幅)では再現できない情報である。

以上、 分位点回帰の本質(中央値推定・ピンボール非対称損失・予測区間)を 3 枚の図で整理した。 詳細は本文の各セクションを参照。

🎮 触って理解する

下の散布図は 教材用の擬似データ(x が大きいほど y のばらつきが増える 不均一分散 データ)。 スライダーで分位点 τ を動かすと、 その分位点を通る回帰直線が リアルタイム に引き直される。 散布図を左右にドラッグ(スマホは左右スワイプ)しても τ が変わる。 計算は「最適な分位点回帰直線は少なくとも 2 点を通る」という性質を使い、 全点対を厳密に探索して大域最適解を求めている(近似ではない)。

ピンボール損失 ρ_τ(u) = u·(τ − 𝟙{u<0})

👀 ここに注目

📐 数式 — Koenker & Bassett (1978) の定式化

🍰 まずはやさしく

計算のルールを決めた数式のことです。

正確に予測値を出すための基準を作るために使います。

スマホの利用時間の上位と下位を数式で分けます。

ここでは分位点回帰の数学的な定義について読みます。

分位点の定義

【$\tau$ 分位点】
$$Q_\tau(Y) = \inf\bigl\{y : F_Y(y) \ge \tau\bigr\}, \quad \tau \in (0,1)$$
分布関数 $F_Y$ が $\tau$ を超える最小の $y$。 $\tau=0.5$ で中央値、 $\tau=0.9$ で上位 10% 点。

pinball loss(チェック関数)

【pinball loss $\rho_\tau$】
$$\rho_\tau(u) = u \cdot \bigl(\tau - \mathbb{1}\{u < 0\}\bigr) = \begin{cases} \tau \cdot u & u \ge 0 \\ (\tau - 1) \cdot u & u < 0 \end{cases}$$
残差 $u = y - \hat{y}$ の符号で重みが変わる非対称な絶対値関数。 $\tau$ が大きいほど「予測が小さすぎる」ペナルティが重い。

分位点回帰の最適化問題

【最適化】
$$\hat{\boldsymbol\beta}(\tau) = \arg\min_{\boldsymbol\beta \in \mathbb{R}^p} \sum_{i=1}^{n} \rho_\tau\bigl(y_i - x_i^\top \boldsymbol\beta\bigr)$$
$\tau$ ごとに別の係数ベクトル $\hat{\boldsymbol\beta}(\tau)$ が推定される。 線形計画問題として解く。

OLS との対比:損失関数の違い

【OLS は二乗誤差最小化】
$$\hat{\boldsymbol\beta}_{\text{OLS}} = \arg\min_{\boldsymbol\beta} \sum_{i=1}^{n} (y_i - x_i^\top \boldsymbol\beta)^2$$
中央値回帰($\tau=0.5$)は絶対誤差最小化: $$\hat{\boldsymbol\beta}(0.5) = \arg\min_{\boldsymbol\beta} \sum_{i=1}^{n} |y_i - x_i^\top \boldsymbol\beta|$$
$\tau=0.5$ のとき pinball は対称になり、 普通の絶対値関数(L1 損失)に一致。 これが LAD 回帰(Least Absolute Deviations)の正体。

🔬 数式を「言葉」で読み解く

$\tau$(タウ)
狙う分位点。 0 〜 1 の値。 $\tau=0.5$ で中央値、 $\tau=0.1$ で下位 10%、 $\tau=0.9$ で上位 10%。
$u_i = y_i - x_i^\top\boldsymbol\beta$
残差。 「実測値 − 予測値」。 正なら「予測が小さすぎた」、 負なら「予測が大きすぎた」。
$\rho_\tau(u)$(pinball loss)
非対称なペナルティ関数。 「下からのズレ」と「上からのズレ」に異なる重み を与える。 $\tau=0.9$ のときは「予測が小さすぎる」ペナルティが「大きすぎる」の 9 倍。
$\hat{\boldsymbol\beta}(\tau)$
τ ごとに推定される回帰係数。 τ により 係数の値そのものが変わる のがポイント。 これが分位点回帰の威力。
$Q_\tau(Y \mid X = x)$
「説明変数が $x$ という条件下での Y の τ 分位点」。 OLS の $E[Y \mid X]$ と並ぶ位置にある。
L1 vs L2 損失
OLS は L2(二乗誤差)、 分位点回帰は L1 系(絶対値)。 L1/L2 はノルムの種類に対応し、 L1 系は 外れ値の影響を受けにくい(外れ値の残差が大きくても二乗されない)。

🧮 SSDSE-B 都道府県データで計算してみる

SSDSE-B 2026 の 2023 年データを用い、 「延べ宿泊者数 $y$ ~ 人口総数 $x$」 の分位点回帰を τ = 0.10, 0.50, 0.90 の 3 本で推定します。

STEP 0: 何が知りたいか

普通の回帰なら 1 本の直線で「人口が 1 万人増えると延べ宿泊者数は何万人泊増えるか(平均)」を見ます。 しかし都道府県では 大都市・観光地と地方で 1 人あたり宿泊者数が大きく違う ので、 「平均だけ」では情報を失います。

STEP 1: 数値の準備(標本値の代表例)

説明変数 $x$ = 人口総数(万人)、 被説明変数 $y$ = 延べ宿泊者数(万人泊)。 5 県を抜粋:

都道府県$x$(万人)$y$(万人泊)$y/x$(1人あたり 人泊)
東京140980275.70
大阪87644015.02
愛知74817212.30
北海道50932786.44
沖縄147200413.65

STEP 2: 3 本の分位点回帰直線(47 県全部で推定した典型結果)

τ=0.10 下位 10% 直線
$\hat{y} = 45.4 + 2.13 \cdot x$(傾き:1 万人増えるごとに 2.13 万人泊)
τ=0.50 中央値直線
$\hat{y} = -35.6 + 4.08 \cdot x$(傾き:1 万人増えるごとに 4.08 万人泊)
τ=0.90 上位 10% 直線
$\hat{y} = 179.1 + 5.57 \cdot x$(傾き:1 万人増えるごとに 5.57 万人泊)

STEP 3: 解釈

🧮 ピンボール損失 (Pinball Loss) の幾何学

分位点回帰が最小化する目的関数は ピンボール損失 (チェック損失とも呼ぶ) です。 τ ∈ (0, 1) のとき、 残差 u = y − ŷ に対し:

$$ \rho_\tau(u) = \begin{cases} \tau \cdot u & (u \geq 0) \\ -(1-\tau) \cdot u & (u < 0) \end{cases} $$

これは「予測が下振れ (u>0) したときのペナルティ τ」と「上振れ (u<0) したときのペナルティ 1-τ」を非対称に課す関数で、 τ=0.5 のとき左右対称となり中央値回帰 (LAD 回帰) と一致します。 τ=0.9 なら下振れ予測を 9 倍重く罰し、 結果として「90% 分位」の上を通る線が得られます。

🐍 ピンボール損失を手書きして 5 本の τ-線を最小化

このコードでやること: sklearn.linear_model.QuantileRegressor を用いて、 ピンボール損失の数値最小化を直接行う。 statsmodels の内部実装 (LP based) と異なり scikit-learn は L1 正則化と組み合わせて使うことで スパース分位点回帰も実現できる。

📥 入力データ (47 県の人口 → 延べ宿泊者数):

県コード 人口(万人) 延べ宿泊者数(万人泊) R01000 509.2 3278.3 R13000 1408.6 8027.4 R47000 146.8 2003.8 ... ... ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
🐍 sklearn_quantreg_pinball.py<button onclick="copyTryitCode(this)">📋 コピー</button></div><div class="pygblock">import pandas as pd
from sklearn.linear_model import QuantileRegressor
from sklearn.metrics import mean_pinball_loss
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = (df[['A1101']] / 1e4).values
y = (df['G7101'] / 1e4).values
for q in [0.10, 0.50, 0.90]:
    m = QuantileRegressor(quantile=q, alpha=0.0, solver='highs').fit(X, y)
    loss = mean_pinball_loss(y, m.predict(X), alpha=q)
    print(f'tau={q:.2f}  pinball_loss={loss:7.2f}  coef={m.coef_.round(4)}')</div>

📤 実行すると次の出力が得られる:

tau=0.10 pinball_loss= 72.93 coef=[2.1325] tau=0.50 pinball_loss= 200.96 coef=[4.0761] tau=0.90 pinball_loss= 109.38 coef=[5.5717]

💬 ピンボール損失は τ=0.5 で最大、 両端 (τ=0.1, 0.9) で小さくなります。 これは「中央値推定は全データの絶対値偏差和を最小化するので大きく、 両端は片側 10% だけ責任を持つので小さい」という幾何学的な帰結です。 業務応用では、 例えば 需要予測の安全在庫 (τ=0.95) や VaR 推定 (τ=0.01) など、 τ を業務 KPI に合わせて設計します。

🧮 数式に値を入れて手で計算する: 分位点回帰の損失

合成データで τ=0.5 (中央値回帰) の損失を計算する。

Step 1: 損失関数

ρ_τ(u) = u(τ - I[u<0]) 中央値: τ=0.5 → ρ = 0.5·|u|

Step 2: 5 点で計算

eρ_0.5ρ_0.9 (高位)
+21.01.8
+10.50.9
000
-10.50.1
-21.00.2

Step 3: 損失合計

τ=0.5: 1.0+0.5+0+0.5+1.0 = 3.0 τ=0.9: 1.8+0.9+0+0.1+0.2 = 3.0 中央値 τ=0.5 は対称、 τ=0.9 は上側残差を重視

🐍 Python で再現

1
2
3
4
5
import numpy as np
e = np.array([2, 1, 0, -1, -2])
def rho(u, tau): return u * (tau - (u < 0).astype(float))
print(f"τ=0.5: {rho(e, 0.5).sum()}")
print(f"τ=0.9: {rho(e, 0.9).sum()}")

📤 実行結果

τ=0.5: 3.0 τ=0.9: 3.0

💬 手計算 (Step 3) と Python 出力が完全一致。

🐍 Python 実装 — statsmodels と LightGBM の 2 通り

1. statsmodels (古典的・線形分位点回帰)

🎯 このコードでやることstatsmodelsquantreg で SSDSE-B 2026 都道府県人口→延べ宿泊者数の関係を τ=0.10/0.50/0.90 の 3 分位点で線形回帰。 中央値 (50%) と下位 10%・上位 90% の回帰係数の違いから「散らばりが人口で広がるか」を読む。

📥 入力:SSDSE-B-2026.csv の 2023 年データ (47 都道府県)。 列 A1101=人口 → 万人単位 / G7101=延べ宿泊者数 → 万人泊単位に換算。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt

# SSDSE-B 2026 を読み込み、2023 年に絞る
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]).rename(columns={'SSDSE-B-2026': '年度'})
df = df[df['年度'] == 2023].copy()
df = df.rename(columns={'A1101': '人口', 'G7101': '延べ宿泊者数'})

# 単位を万人・万人泊に揃える(数値の桁を整える)
df['人口万人'] = df['人口'] / 1e4
df['宿泊万人泊'] = df['延べ宿泊者数'] / 1e4

# 3 つの分位点で回帰
taus = [0.10, 0.50, 0.90]
models = {}
for tau in taus:
    mod = smf.quantreg('宿泊万人泊 ~ 人口万人', data=df)
    res = mod.fit(q=tau)
    models[tau] = res
    print(f'τ={tau}: intercept={res.params[0]:.2f}, slope={res.params[1]:.3f}')

📤 実行例

τ=0.10: intercept=45.45, slope=2.132 τ=0.50: intercept=-35.63, slope=4.076 τ=0.90: intercept=179.10, slope=5.572

💬 結果の読み方:slope (人口万人 → 延べ宿泊者数 万人泊) が τ=0.10 で 2.13、 τ=0.90 で 5.57 と増加 → 人口が同じでも上位 (90 パーセンタイル) ほど人口あたりの宿泊者数が多い。 OLS なら 1 本の回帰直線だが、 分位点回帰は「散らばりが人口に依存して扇形に広がる」構造を可視化できる。

2. 結果の可視化(扇形に広がる予測区間)

 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
fig, ax = plt.subplots(figsize=(9, 6))

# 散布図
ax.scatter(df['人口万人'], df['宿泊万人泊'],
           s=70, alpha=0.6, edgecolor='white', linewidth=1.2,
           color='#1565C0', label='47 都道府県 (2023)')

# 3 本の分位点直線
x_grid = np.linspace(df['人口万人'].min(), df['人口万人'].max(), 100)
colors = {0.10: '#D32F2F', 0.50: '#388E3C', 0.90: '#D32F2F'}
styles = {0.10: '--', 0.50: '-', 0.90: '--'}
for tau, res in models.items():
    a, b = res.params
    ax.plot(x_grid, a + b * x_grid,
            color=colors[tau], linestyle=styles[tau], linewidth=2.2,
            label=f'τ = {tau:.2f}')

# 比較のため OLS も
import statsmodels.formula.api as smf
ols = smf.ols('宿泊万人泊 ~ 人口万人', data=df).fit()
ax.plot(x_grid, ols.params[0] + ols.params[1] * x_grid,
        color='#1976D2', linewidth=2.2, linestyle=':', label='OLS (平均)')

ax.set_xlabel('人口総数(万人)')
ax.set_ylabel('延べ宿泊者数(万人泊)')
ax.set_title('分位点回帰:延べ宿泊者数 ~ 人口(10/50/90 パーセンタイル)')
ax.legend(loc='upper left')
plt.tight_layout()
plt.show()

3. LightGBM での非線形分位点回帰

大きなデータや非線形関係には、 LightGBM の objective='quantile' が高速で便利です。

 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
from lightgbm import LGBMRegressor

# 同じデータで 3 本の分位点モデルを学習
X = df[['人口万人']].values
y = df['宿泊万人泊'].values

quantile_models = {}
for tau in [0.10, 0.50, 0.90]:
    m = LGBMRegressor(objective='quantile', alpha=tau,
                       n_estimators=300, learning_rate=0.05,
                       num_leaves=15, random_state=42, verbose=-1)
    m.fit(X, y)
    quantile_models[tau] = m

# 各 τ で予測(同じ x_grid 上)
X_grid = x_grid.reshape(-1, 1)
preds = {tau: m.predict(X_grid) for tau, m in quantile_models.items()}

# 予測区間として fill_between
fig, ax = plt.subplots(figsize=(9, 6))
ax.fill_between(x_grid, preds[0.10], preds[0.90],
                alpha=0.18, color='#D32F2F', label='80% 予測区間')
ax.plot(x_grid, preds[0.50], color='#388E3C', linewidth=2.3, label='中央値')
ax.scatter(df['人口万人'], df['宿泊万人泊'], s=60, alpha=0.6, color='#1565C0')
ax.set_xlabel('人口(万人)'); ax.set_ylabel('延べ宿泊者数(万人泊)')
ax.legend(); plt.tight_layout(); plt.show()

4. pinball loss を直接計算して評価

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) 北海道 5,092,000 1,681,000 24,430 東京都 14,086,000 3,205,000 86,348 沖縄県 1,468,000 350,000 12,549 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ── この抜粋で使うモデルを用意します ──
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_pinball_loss

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年の 47 都道府県
X = df[['A1101', 'A1303']].astype(float).values
y = df['A4101'].astype(float).values          # 出生数
quantile_models = {q: GradientBoostingRegressor(loss='quantile', alpha=q,
                                                random_state=0).fit(X, y)
                   for q in (0.10, 0.90)}


# τ=0.9 モデルの平均 pinball loss
y_pred_90 = quantile_models[0.90].predict(X)
loss_90 = mean_pinball_loss(y, y_pred_90, alpha=0.90)
print(f'τ=0.90 の pinball loss: {loss_90:.2f}')

# カバレッジ(予測区間に入った観測値の割合)
y_lo = quantile_models[0.10].predict(X)
y_hi = quantile_models[0.90].predict(X)
coverage = ((y >= y_lo) & (y <= y_hi)).mean()
print(f'80%予測区間のカバレッジ: {coverage*100:.1f}%')  # 理論値 80%

5. 多変量への拡張(複数説明変数)

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) B4101(年平均気温) G7101(延べ宿泊者数) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 11.0 32,783,470 296,888 東京都 14,086,000 17.6 80,273,650 341,320 沖縄県 1,468,000 23.8 20,038,190 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# ── この抜粋で使うデータを用意します ──
import pandas as pd
import statsmodels.formula.api as smf

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年の 47 都道府県
df = df.copy()
df['宿泊万人泊'] = df['G7101'].astype(float) / 1e4   # 延べ宿泊者数
df['人口万人'] = df['A1101'].astype(float) / 1e4

# 人口 + 気温 + 消費支出(万円)で 3 つの分位点を推定
df['気温'] = df['B4101']
df['消費支出万円'] = df['L3221'] / 1e4

for tau in [0.10, 0.50, 0.90]:
    res = smf.quantreg('宿泊万人泊 ~ 人口万人 + 気温 + 消費支出万円',
                       data=df).fit(q=tau)
    print(f'τ={tau}:')
    print(res.params.round(4))
    print()

⚠️ 5 つの「分位点回帰 落とし穴」

① 分位点の交差 (quantile crossing)
別々に推定した τ=0.10 と τ=0.50 の直線が 交差してしまう ことがある(数学的には τ=0.10 ≤ τ=0.50 ≤ τ=0.90 が常に成り立つはずなのに)。
対処:① 単調性制約を入れた 合同分位点回帰 (composite quantile) を使う、 ② すべての τ を同時推定する monotonic neural quantile regression、 ③ 推定後に並び替え(rearrangement)。
② τ が極端(0.01 や 0.99)だとサンプル不足で不安定
τ=0.99 を推定するには「上位 1% のデータ」が信頼できるほどあることが必要。 n=47 の都道府県データで τ=0.95 を推定するのは 事実上不可能
対処:① τ を 0.1〜0.9 の範囲に制限、 ② サンプルサイズを増やす、 ③ 極値理論(EVT) でテール分布を別途モデル化。
③ 標準誤差・信頼区間の計算が複雑
pinball loss は 微分不可能なので、 OLS のように単純な公式で標準誤差が出ない。 statsmodels はデフォルトでブートストラップではなく漸近近似を使うため、 小標本では誤差が大きい。
対処:① res.summary() の SE は参考程度に、 ② ブートストラップ法n_boot=1000)で再サンプリングして信頼区間を計算するのが正攻法。
④ τ=0.5 と OLS の違いを過小評価しない
「中央値回帰 ≈ OLS」と思いがちだが、 外れ値があるとき両者は大きく違う(OLS は外れ値に引っ張られる)。 SSDSE-B のように東京が極端なデータでは、 OLS と τ=0.5 の傾きが 30% 以上違うこともある。
対処:必ず両方推定して、 「平均としての関係 vs 典型値としての関係」を比較する。
⑤ LightGBM の objective='quantile' は τ ごとに別モデル
LightGBM では τ=0.1, 0.5, 0.9 のために 3 つの独立したモデル を学習する必要があり、 学習時間も 3 倍、 メモリも 3 倍。 さらに 分位点の交差問題 が起きやすい。
対処:① NGBoostconformal prediction を検討、 ② quantile-forest パッケージは 1 モデルで全 τ を出せる、 ③ 結果を rearrange して単調化。

🗺 概念マップ — 分位点回帰の位置づけ

階層 概念 関係
最上位回帰分析分位点回帰はその 1 流派
同列OLS, ロバスト回帰, GAMいずれも条件付き分布の何かを推定
特殊例中央値回帰(τ=0.5)= LAD 回帰L1 損失最小化と等価
前提分位点、 pinball loss、 線形計画統計と最適化の交点
応用予測区間、 リスク分析(VaR)、 不平等指標経済・金融・公衆衛生で頻出
機械学習版分位点 RF, LightGBM-quantile, NGBoost非線形化と高次元対応
分位点回帰 LAD 回帰 Huber 回帰 分位点ランダムフォレスト NGBoost conformal pred Lasso 分位点回帰

🔗 隣接手法への橋渡し

「分位点回帰」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。

SSDSE-B-2026 の延べ宿泊者数を分位点回帰で予測すると、 中央値 (τ=0.5) と上位 (τ=0.9) で人口の係数が異なり、 「人口が増えると上位県ほど宿泊者数が急増」の不均一性が定量化できる。

🌳 手法選択フロー

「分位点回帰」を実際の課題に当てはめるとき、 状況別に何を選ぶかを 3 段階で判定する。

  1. 分布の形は対称か? 対称 → OLS 回帰で十分、 非対称 (人口・所得) → 分位点回帰
  2. 関心は中央値か上下端か? 中央値 → 0.5 分位、 上位 → 0.9、 下位 → 0.1 を指定
  3. 外れ値はあるか? 多 → 分位点回帰の頑健性が活きる、 少 → OLS と結果が近い

SSDSE-B-2026 の延べ宿泊者数を人口で回帰する際、 OLS は東京の影響で傾きが過大評価される。 0.5 分位回帰なら中央値ベースの「典型的な県の人口-宿泊者数 関係」が得られる。

🔎 解説深化 — 「1 点の巨人」で試すロバスト性

このページの本文・ウィジェットは ピンボール損失で条件付き分位点を引くという 分位点回帰の理論を丁寧に扱っている。ここでは重複を避け、 「たった 1 つの極端な観測(東京都)を抜くと、OLS と中央値回帰でどれだけ結果がブレるか」 という 実データによるストレステスト の角度から深掘りする。数値は data/raw/SSDSE-B-2026.csv の 2023 年・47 都道府県、 A1101 総人口 を説明変数、G7101 延べ宿泊者数 を目的変数として 実際に statsmodels で推定した実測値のみを載せる。

🎨 直感 — 平均は「引っぱられる」、中央値は「踏ん張る」

東京都は総人口 14.086 百万人・延べ宿泊者数 80.27 百万人泊 と、 他県から大きく離れた 高レバレッジ点(1 点の巨人) だ。 平均を最小化する OLS はこの 1 点に敏感で、まるで綱引きのように傾きが引っぱられる。 一方、中央値回帰(τ=0.5)は「順位」だけを見るので、 極端な 1 点が上に飛ぼうが下に飛ぼうが、それが多数派の並びを崩さない限り線はほとんど動かない。 下の表は、その「踏ん張り」を実測で示したものだ。

推定(延べ宿泊者数 ~ 総人口, 百万単位) 全 47 県の傾き 東京を除く 46 県の傾き 変化率
OLS(条件付き平均) 4.106 2.935 −28.5%
中央値回帰(τ=0.5) 4.076 3.503 −14.1%

同じ「東京を 1 県抜く」という操作で、OLS の傾きは 28.5% も動くのに対し、 中央値回帰は 14.1%——およそ半分の揺れで済む。 これが「中央値回帰は外れ値・高レバレッジ点にロバスト」という主張の、実データでの姿だ。 なお全 47 県では両者の傾きが 4.106 と 4.076 とほぼ一致している点も面白い: 東京がたまたま回帰線の近くに乗っているうちは差が出ず、差は「抜いたとき」に初めて露見する

⚠️ 落とし穴(重要)— 分位点クロッシングは「データの外」で起こる

τ=0.1 / 0.5 / 0.9 を独立に推定すると、3 本の直線の傾き・切片は次のようになる(2023 年・実測)。

観測されている総人口の範囲(最小は 鳥取県 0.537 百万人〜最大は東京 14.086 百万人)の 内側では、q10 ≤ q50 ≤ q90 の順序は一度も崩れない (鳥取・中央値県・東京のいずれの人口でも予測が正しく並ぶことを確認済み)。 ところが τ=0.1 と τ=0.5 の 2 直線を延長すると、 総人口 0.417 百万人のあたりで交差する。 これは観測最小の 0.537 百万人より 外側(データの台の外) だ。

📝 教訓:分位点クロッシング(下位分位の予測が上位分位を上回る破綻)は、 多くの場合 データが薄い/存在しない領域への外挿で顔を出す。 「クロスした=手法が壊れた」ではなく、「そこは推定を信じてはいけない領域」だと教えてくれる警報と読むのが正しい。 実務では観測範囲内の予測に限定するか、単調性制約付き推定(本文の Crossing 修正パート参照)で対処する。

🚀 発展 — 「区間の幅」そのものを説明変数の関数にする

上の 3 本から、任意の人口 x に対する 80% 予測区間[q10(x), q90(x)] として組める。幅は (1.791−0.454) + (5.572−2.132)·x = 1.337 + 3.440·x となり、 人口が大きい県ほど区間幅が線形に広がる。 これは OLS+等分散仮定では決して得られない情報だ—— OLS の予測区間は全 x で(ほぼ)一定幅を仮定するのに対し、 分位点回帰は 「ばらつきの大きさ自体が x で変わる」不等分散を、モデルの出力として直接返す。 延べ宿泊者数のように「大都市ほど年ごと・施設ごとの振れ幅が大きい」データでは、この性質が予測の実用価値を決める。

さらに一歩進めるなら、τ を 0.05〜0.95 まで連続的に動かして係数の軌跡を描く τ-プロセス(quantile process)が、 「人口の効き方が分位によってどう変わるか」を 1 枚で見せてくれる (本文の τ-process 相当パートを参照)。ここでも傾きは τ とともに 2.132 → 4.076 → 5.572 と 単調に増加しており、「上位県ほど人口 1 単位あたりの宿泊者数の伸びが大きい」という不均一性が読み取れる。

※ 本節の数値はすべて SSDSE-B-2026(2023 年・47 都道府県、列 A1101G7101)を statsmodels で実際に推定した実測値。合成・架空データは使用していない。