論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
重回帰分析
Multiple Regression Analysis
複数の説明変数で目的変数を予測。 各偏回帰係数 βⱼ は「他の変数を一定にしたときの xⱼ の純粋な効果」。
回帰モデルOLSy=β₀+Σβⱼxⱼ偏回帰係数多変量BLUE

🔖 キーワード索引

この用語ページの主要トピックを一覧から飛べます。

📍 文脈💡 30秒結論🎨 直感📐 数式・定義🔬 数式の読み解き🧮 SSDSE-B-2026 計算🐍 Python 実装⚠️ 落とし穴🌐 関連手法🔗 関連用語📚 グループ教材🗺 概念マップ📜 歴史と系譜🔧 実装詳細⚙️ 運用とトラブル💴 コストと見積もり🛡 ガバナンスとセキュリティ🏭 産業事例📊 比較表📝 演習💥 失敗例📖 用語辞典📚 参考文献

💡 30秒で分かる結論

🍰 まずはやさしく

複数の原因から結果を予想する道具です。

何がどれくらい影響しているか調べます。

テストの点数を勉強時間と睡眠時間で予想します。

この章では分析の結論を出す方法を学びます。

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

🍰 まずはやさしく

統計の分析でよく使われる基本の手法です。

複雑なデータの関係を整理するために使います。

地域の健康状態を色々なデータで分析します。

この章ではこの手法がどう使われるかを見ます。

論文や報告書で 「重回帰分析」「multiple regression」「OLS による多変量回帰」「偏回帰係数」「調整済み R²」「Ridge / Lasso 回帰」 といった表現が出てきたら、 このページが該当します。

本コンペでは、 都道府県や市町村の 死亡率・人口減少率・経済成長率 といったマクロ指標を、 高齢化率・産業構造・気候・所得・医療資源 など複数の説明変数で説明したいときに毎回登場します。 「単相関で見えていた関係が、 第3変数を入れたら消える」現象を 統計的に処理するための 基幹技術 です。

用語の系譜: 単回帰(説明変数 1 個)の素直な一般化が重回帰。 そこから一般化線形モデル(GLM)/ロジスティック回帰/ポアソン回帰/一般化加法モデル(GAM)/混合効果モデル/パネルデータモデルへと枝分かれしていく。 つまり「重回帰を理解する」ことは、 統計モデリングの森の を押さえることに等しい。

単相関は「2 変数の散布図に直線を引く」絵で済みますが、 重回帰は「3 次元以上の超平面」を引きます。 視覚化はできなくても、 数式の上では同じ「最小二乗法(OLS)」で解けます。 教育的には p=2(説明変数 2 個=3 次元)の絵を一度しっかり描く ことで、 高次元に対する直感を養いやすくなります。

なお、 本ページでは OLS による重回帰 を主軸に扱い、 Ridge/Lasso/Elastic Net などの正則化版や、 ロジスティック・GAM といった非線形化は 関連手法セクション でリンクします。 因果推論的な利用は パネル因果 へ。

🎨 直感で掴む

🍰 まずはやさしく

データの世界に平らな面を引くイメージです。

見かけ上の関係にだまされないために使います。

気温と死亡率の関係を高齢化率で考え直します。

この章では図や例を使って仕組みを理解します。

単回帰は「散布図に 直線 を引く」絵で理解できます。 では説明変数が 2 個になったら? y, x₁, x₂ の 3 次元空間で 平面 を引くイメージです。 3 個以上だと視覚化はできませんが、 数式上は 超平面 を引いていることになります。

🎬 ストーリー:47 都道府県の死亡率を 4 変数で説明する

SSDSE-B-2026 で死亡率(A4200 系の代理指標)を 4 変数で説明したい:

単純に「死亡率 vs 求人倍率」の単相関を取ると、 たとえば r = +0.31 と出るかもしれません。 でも本当に「不景気 → 死亡率上昇」なのでしょうか? 実は東北など雪国では同時に高齢化が進んでいる。 つまり「高齢化率」という第 3 変数が両方を動かしている 交絡(confounding) の典型です。

そこで重回帰の出番。 x₁(高齢化率)を式に同時投入すると、 求人倍率の偏回帰係数 β₃ は「高齢化を一定にしたうえでの求人倍率の追加効果」になり、 多くの場合大きく縮みます。 偏回帰係数こそ、 政策議論で本当に欲しい数値。 これが重回帰の威力です。

🎨 視覚的比喩:「点雲を斜めに切るハサミ」

47 都道府県を 4 次元空間に浮かべた 点雲とイメージしてください。 重回帰は、 この雲を 最も低い残差二乗和で貫く超平面 をハサミで探す作業。 ハサミで切った断面が「y の予測値 ŷ」になります。 各説明変数の β は その軸方向への平面の傾き に対応します。

🎬 もう一つの例:ラーメン屋の売上を 3 変数で予測

理論を覚える前に、 もっと身近な例を 1 つ:

「単純に客数だけで回帰したら β = 950 円」だったとしても、 重回帰で気温・曜日を入れると β = 1,050 円かもしれない。 なぜなら、 暑い休日には客数だけでなく 客単価も上がる 傾向があり、 単回帰は「客数だけ」の効果ではなく「客数+それに付随するもの」を混ぜていたから。 重回帰で他要因を一定にして、 ようやく 客 1 人あたりの真の売上貢献が見えてくる。

📐 平面の式を絵にする — p=2 の具体例

$\hat{y} = 2 + 3x_1 - 1.5x_2$ を 3 次元空間で描くと、 切片 2 で、 x₁ 方向に 3 ずつ上がり、 x₂ 方向に 1.5 ずつ下がる「斜めの板」になります。 残差は各点から板までの「縦方向の距離」。 これらを二乗して合計した値が最小になる板を選ぶのが OLS です。 視覚化ツール(plotly や matplotlib の 3D 散布図)で一度自分の手で描いてみると、 偏回帰係数の「軸方向の傾き」という意味が腑に落ちます。

🎬 第三の例:マンション価格モデル(不動産業界の主軸)

「マンション価格を予測する」のは典型的な重回帰タスク。 説明変数として築年数・面積・駅距離・階数・周辺所得・最寄り路線などを使う。 例えば

$\hat{\text{価格}} = 800 + 30 \cdot \text{面積}_{m^2} - 5 \cdot \text{築年数} - 2 \cdot \text{駅距離}_{分} + 10 \cdot \text{階数} + \dots$

これだと「同じ面積・駅距離・階数・所得帯のマンションで比べたとき、 築 1 年増えるごとに価格が 5 万円下がる」という解釈ができる。 査定エンジンの古典的バックボーン。 SUUMO や HOMES の自動査定 API はこの拡張版を使っている。

🎨 R282 補強: 重回帰の視覚的理解

重回帰分析を「式」だけでなく「形」で掴むために、 4 つの観点から視覚化を整理する。 ここでは SSDSE-B-2026(47 都道府県)で実際に確認できる現象を中心に扱う。

1. 単相関 vs 偏相関の対比

単相関は「他の変数を一切無視した素朴な関係」、 偏相関は「他の変数を統計的に固定したうえでの純粋な関係」。 例: 死亡率 y と平均気温 x₄ の単相関は r = −0.44(寒い県ほど死亡率が高い)。 しかし高齢化率 x₁ を固定すると偏相関は ≈ 0 に縮む → 気温の効果は実は高齢化率の影づくり。

変数単相関 r(y, xⱼ)偏相関 r(y, xⱼ | 残り)解釈
x₁ 高齢化率+0.972+0.909本物の効果
x₂ 病院数+0.524−0.038共通要因で見かけだけ
x₃ 新規求職申込件数−0.594+0.215疑似相関(弱い)
x₄ 気温−0.442+0.051疑似相関

2. 残差プロットで仮定診断

残差 eᵢ = yᵢ − ŷᵢ を予測値 ŷᵢ の関数としてプロットしたとき、 ランダムに散らばっていれば線形性・等分散性が概ね成立。 曲線パターンが見えたら非線形項追加、 ラッパ状なら HC3 ロバスト SE か WLS。

残差プロットの形診断対処
無方向に散布仮定OKそのままで OK
U 字 / 逆 U 字線形性違反多項式項、 GAM
ラッパ状(広がる)等分散性違反HC3 ロバスト SE、 log(y)
周期パターン独立性違反Newey-West、 ARIMA
外れ値突出影響観測Cook 距離、 ロバスト回帰

3. VIF と多重共線性

VIFⱼ = 1 / (1 − Rⱼ²) は「変数 xⱼ を残りの説明変数で回帰したときの説明されにくさの逆数」。 5 を超えたら警戒、 10 で深刻。 多重共線性が深刻な場合、 個別の β̂ は不安定だが予測そのものは依然有効、 という点を見落とさない。

4. 標準化係数で重要度比較

単位が異なる x(人口、 温度、 倍率)を直接比較できないため、 z 標準化した X で回帰し直すか、 β*ⱼ = β̂ⱼ × (sₓⱼ / s_y) を計算する。 SSDSE 例では x₁: +1.02 が圧倒的、 x₂-x₄ は ≤ 0.07 で実質寄与しない。

これら 4 つの視点を毎回確認すれば、 重回帰の出力を「数字の羅列」から「物語」に変えられる。

5. 🐍 補強コード(R282)

このコードでやること: SSDSE-B-2026 を読み込み、 4 説明変数で重回帰し、 偏相関・残差プロット・VIF・標準化係数を一気に出す。

📥 入力: data/raw/SSDSE-B-2026.csv(cp932、 2023 年抽出)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import pandas as pd, numpy as np
from statsmodels.regression.linear_model import OLS
from statsmodels.stats.outliers_influence import variance_inflation_factor as vif

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()
y = df['A4200'] / df['A1101'] * 1000           # 死亡率 (千分率)
X = pd.DataFrame({
    'age':  df['A1303'] / df['A1101'] * 100,    # 高齢化率 %
    'hosp': df['I510120'] / df['A1101'] * 1e5,  # 10万人あたり病院数
    'job':  df['F3101'],                         # 新規求職申込件数
    'temp': df['B4101'],                         # 平均気温
})
X = pd.concat([pd.Series(1, index=X.index, name='const'), X], axis=1)
res = OLS(y, X).fit()
print(res.summary())
print('VIF:', {c: round(vif(X.values, i), 2) for i, c in enumerate(X.columns) if c != 'const'})

📤 実行例:

R² = 0.947 Adj-R² = 0.943 β₁(age) = +0.649 (SE 0.046, t=14.09, p<0.001) β₂(hosp) = -0.010 (SE 0.038, t=-0.25, p=0.804) β₃(job) = +2.9e-06 (SE 2.0e-06, t=1.43, p=0.161) β₄(temp) = +0.017 (SE 0.052, t=0.33, p=0.743) VIF: {'age': 4.31, 'hosp': 2.07, 'job': 2.1, 'temp': 2.09}

💬 高齢化率だけが圧倒的に有意。 他 3 変数は単相関では見えても重回帰では消える → 疑似相関。

このコードでやること: 偏相関係数を全変数ペアで計算し、 単相関 r と比較する。

📥 入力: 同じ X, y

1
2
3
4
5
6
7
8
from pingouin import partial_corr

for v in ['age', 'hosp', 'job', 'temp']:
    covars = [c for c in ['age','hosp','job','temp'] if c != v]
    r_simple = X[v].corr(y)
    pc = partial_corr(data=pd.concat([y.rename('y'), X.drop(columns='const')], axis=1),
                       x=v, y='y', covar=covars)
    print(f'{v}: r={r_simple:+.3f}  partial r={pc["r"].iloc[0]:+.3f}')

📤 実行例:

age: r=+0.972 partial r=+0.909 hosp: r=+0.524 partial r=-0.038 job: r=-0.594 partial r=+0.215 temp: r=-0.442 partial r=+0.051

💬 偏相関により「他変数を一定にした純粋な効果」が見える。 気温の効果は消える。

このコードでやること: 残差プロットと QQ プロットで仮定診断。

📥 入力: res(前段の OLS 結果オブジェクト)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import matplotlib.pyplot as plt
import scipy.stats as stats

fig, ax = plt.subplots(1, 2, figsize=(10, 4))
ax[0].scatter(res.fittedvalues, res.resid)
ax[0].axhline(0, color='red', ls='--')
ax[0].set_xlabel('Fitted'); ax[0].set_ylabel('Residual')
ax[0].set_title('Residual vs Fitted')

stats.probplot(res.resid, dist='norm', plot=ax[1])
ax[1].set_title('Normal QQ Plot')

plt.tight_layout(); plt.savefig('multireg_diagnostics.png', dpi=130)

📤 実行例: 残差は方向性なく散布、 QQ プロットはほぼ直線 → 仮定 OK

💬 残差プロットで曲線や広がりが見えなければ、 線形性・等分散性は許容範囲。

このコードでやること: 標準化係数で重要度を可視化。

📥 入力: 同じ X, y

1
2
3
4
5
6
7
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression

Xs = StandardScaler().fit_transform(X.drop(columns='const'))
ys = (y - y.mean()) / y.std()
beta_std = LinearRegression().fit(Xs, ys).coef_
print(dict(zip(['age','hosp','job','temp'], beta_std.round(3))))

📤 実行例:

{'age': 1.023, 'hosp': -0.013, 'job': 0.072, 'temp': 0.017}

💬 標準化 β は age が圧倒的(1.02)、 他は ≤0.07 → 実質「高齢化率 1 変数モデル」と等価。

6. 📐 数式に値を入れて手で計算する: β = (X'X)-1X'y の展開

重回帰の核心は 正規方程式 β = (X'X)-1X'y。 これを SSDSE-B-2026 の 47 都道府県データで具体的に値を代入して計算する。 ここでは簡略化のため、 説明変数を 1 つだけ (高齢化率) に絞り、 切片を含む 2 次元線形回帰として手計算する。 47 件の実数値を全て展開すると紙面が膨大になるため、 最初の 5 県のみ 使用した縮小版で計算過程を示す (本来は 47 件で計算)。

Step 1: データ行列 X と目的変数 y の準備

最初の 5 都道府県の (高齢化率 [%], 死亡率 [千分率]) を取り出す:

北海道: age=33.0, death=14.8 青森県: age=35.2, death=17.6 岩手県: age=35.0, death=16.9 宮城県: age=29.2, death=12.7 秋田県: age=39.1, death=19.2

これを X (5×2 行列、 切片列 + 高齢化率) と y (5×1 ベクトル) に並べる:

X = [[1, 33.0], y = [14.8, [1, 35.2], 17.6, [1, 35.0], 16.9, [1, 29.2], 12.7, [1, 39.1]] 19.2]

Step 2: X'X の計算 (2×2 対称行列)

X' は X の転置 (2×5)。 X' × X は (2×5) × (5×2) = (2×2):

X'X[0,0] = 1+1+1+1+1 = 5 X'X[0,1] = X'X[1,0] = 33.0+35.2+35.0+29.2+39.1 = 171.5 X'X[1,1] = 33.0² + 35.2² + 35.0² + 29.2² + 39.1² = 1089.00 + 1239.04 + 1225.00 + 852.64 + 1528.81 = 5934.49 X'X = [[5, 171.5], [171.5, 5934.49]]

Step 3: (X'X)-1 の計算 (2×2 行列の逆行列公式)

2×2 行列 [[a,b],[c,d]] の逆行列は (1/det) × [[d,-b],[-c,a]]、 det = ad - bc。

det = 5 × 5934.49 - 171.5 × 171.5 = 29672.45 - 29412.25 = 260.20 (X'X)⁻¹ = (1/260.20) × [[5934.49, -171.5], [-171.5, 5 ]] = [[ 22.807, -0.6591], [-0.6591, 0.019216]]

Step 4: X'y の計算 (2×1 ベクトル)

X' × y = (2×5) × (5×1) = (2×1):

X'y[0] = 14.8 + 17.6 + 16.9 + 12.7 + 19.2 = 81.2 X'y[1] = 33.0×14.8 + 35.2×17.6 + 35.0×16.9 + 29.2×12.7 + 39.1×19.2 = 488.40 + 619.52 + 591.50 + 370.84 + 750.72 = 2820.98 X'y = [81.2, 2820.98]

Step 5: β = (X'X)-1 X'y を計算

β₀ = 22.807 × 81.2 + (-0.6591) × 2820.98 = 1851.93 - 1859.30 = -7.37 β₁ = (-0.6591) × 81.2 + 0.019216 × 2820.98 = -53.52 + 54.21 = +0.69 → 回帰式: death = -7.37 + 0.69 × age

解釈: 高齢化率が 1% 上がると、 死亡率は 約 0.69 千分率増加。 切片 -7.37 は (age=0 の理論値)、 外挿のため解釈に使わない。 これが手計算で得られた最初の 5 県だけの結果で、 47 県全体ではより精度高い推定 (本文の β₁ ≈ +0.649) になる。

7. 🐍 同じ計算を Python で再現

このコードでやること: 上記 Step 1〜5 を numpy で機械的に実行し、 手計算の結果と完全に一致することを確認する。

📥 入力: 5 都道府県の age と death の数値

 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 numpy as np

# Step 1: データ準備 (5 都道府県の縮小版)
age   = np.array([33.0, 35.2, 35.0, 29.2, 39.1])
death = np.array([14.8, 17.6, 16.9, 12.7, 19.2])

# 切片列を追加した X 行列を作る
X = np.column_stack([np.ones(5), age])
y = death

# Step 2: X'X
XtX = X.T @ X
print("X'X =\n", XtX)

# Step 3: (X'X)^(-1)
XtX_inv = np.linalg.inv(XtX)
print("(X'X)^-1 =\n", XtX_inv)

# Step 4: X'y
Xty = X.T @ y
print("X'y =", Xty)

# Step 5: β = (X'X)^-1 X'y
beta = XtX_inv @ Xty
print("β (切片, 高齢化率) =", beta)
print(f"回帰式: death = {beta[0]:+.2f} + {beta[1]:+.2f} × age")

📤 実行例 (出力):

X'X = [[ 5. 171.5 ] [ 171.5 5934.49]] (X'X)^-1 = [[ 22.807 -0.6591 ] [-0.6591 0.019216]] X'y = [ 81.2 2820.98] β (切片, 高齢化率) = [-7.37 +0.69] 回帰式: death = -7.37 + 0.69 × age

💬 手計算 (上の Step 5) と完全に一致。 numpy の np.linalg.inv は内部で LU 分解を使うため、 5×5 以下の小行列なら手計算との誤差は無視できる。 ただし n が大きいと数値安定性のため np.linalg.solve(XtX, Xty) や QR 分解 (np.linalg.lstsq(X, y, rcond=None)) を推奨。

8. 47 都道府県 全件を使った場合との対比

上の手計算は 5 県の縮小版。 47 県すべてで同じ計算をすると、 サンプルサイズが増えることで標準誤差が縮み、 推定値が安定する。 詳しい結果は本ページ前半 (5. 補強コード R282) の OLS 回帰結果を参照。 5 県 vs 47 県の β₁ を比較:

データ範囲サンプル数β₁ (高齢化率 1% あたり死亡率 [千分率])標準誤差
本セクション手計算5+0.69(計算省略)
R282 補強コード OLS47+0.6490.046

差 0.04 は 小標本誤差。 47 件で計算すれば、 標準誤差 0.046 を満たす中央推定 0.649 が得られる (推定値は ±2 SE で 0.56〜0.74 の範囲)。

📐 数式または定義

🍰 まずはやさしく

数式を使って結果を予測するモデルです。

正確な予測のルールを決めるために使います。

スマホの利用時間と成績の関係を数式にします。

この章では計算に使う数式について学びます。

重回帰モデルの基本形:

$$ y_i = \beta_0 + \beta_1 x_{i1} + \beta_2 x_{i2} + \cdots + \beta_p x_{ip} + \varepsilon_i, \quad i = 1, \dots, n $$

行列形式(設計行列 X は n×(p+1)、 先頭列は切片用の 1):

$$ \mathbf{y} = X\boldsymbol{\beta} + \boldsymbol{\varepsilon}, \quad \boldsymbol{\varepsilon} \sim \mathcal{N}(\mathbf{0}, \sigma^2 I_n) $$

OLS(最小二乗法)の解析解:

$$ \hat{\boldsymbol{\beta}} = \arg\min_{\beta} \sum_{i=1}^n (y_i - X_i^\top \beta)^2 = (X^\top X)^{-1} X^\top \mathbf{y} $$

推定量の分散共分散行列:

$$ \operatorname{Var}(\hat{\boldsymbol{\beta}}) = \sigma^2 (X^\top X)^{-1}, \quad \hat{\sigma}^2 = \frac{1}{n-p-1} \sum (y_i - \hat{y}_i)^2 $$

決定係数と調整済み R²:

$$ R^2 = 1 - \frac{\mathrm{RSS}}{\mathrm{TSS}} = 1 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}, \quad R^2_{\mathrm{adj}} = 1 - (1 - R^2)\frac{n-1}{n-p-1} $$

全体検定(F検定):帰無仮説 H₀: β₁ = β₂ = … = βₚ = 0

$$ F = \frac{R^2 / p}{(1 - R^2) / (n - p - 1)} \sim F(p, n-p-1) $$

個別係数の t検定:

$$ t_j = \frac{\hat{\beta}_j}{\mathrm{SE}(\hat{\beta}_j)} \sim t(n-p-1) $$

VIF(分散拡大因子):x_j を他の説明変数で回帰したときの $R^2_j$ を用いて

$$ \mathrm{VIF}_j = \frac{1}{1 - R^2_j} $$
判定基準: VIF > 5 で警戒、 > 10 で多重共線性が深刻。 すべての説明変数の VIF を必ず確認。

標準化偏回帰係数 β*(変数を z 標準化してから推定):

$$ \beta_j^* = \hat{\beta}_j \cdot \frac{s_{x_j}}{s_y} $$

単位の違う変数間で「影響度」を比較するには β* を使う。

Ridge / Lasso 推定量(参考、 正則化版):

$$ \hat{\boldsymbol{\beta}}_{\mathrm{ridge}} = (X^\top X + \lambda I)^{-1} X^\top \mathbf{y}, \quad \hat{\boldsymbol{\beta}}_{\mathrm{lasso}} = \arg\min \Big\{ \|\mathbf{y} - X\beta\|^2 + \lambda \sum_j |\beta_j| \Big\} $$

Cook's distance(影響度指標):

$$ D_i = \frac{e_i^2}{p \hat{\sigma}^2} \cdot \frac{h_{ii}}{(1 - h_{ii})^2}, \quad h_{ii} = [X(X^\top X)^{-1} X^\top]_{ii} $$

🔬 数式を言葉で読み解く

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

数式 $y_i = \beta_0 + \beta_1 x_{i1} + \cdots + \beta_p x_{ip} + \varepsilon_i$ は暗号ではなく、 1 文字 1 文字に明確な意味があります。 ここでは「重回帰の式を、 統計を知らない人にも説明できるレベルまで」分解します(4 段構え)。

1. 🎯 何を表す式か(一行で)

i 番目の県の死亡率 yᵢ は、 切片 β₀ に、 高齢化率の寄与 β₁x₁ と、 病院数の寄与 β₂x₂ と…… を足したものに、 説明しきれないズレ ε を加えたもの」。 つまり 「目的変数 = 直線的な足し算 + 誤差」 というレシピを書いた式です。 「直線的」とは「説明変数を 2 倍したら y への寄与も 2 倍」という関係。 これが線形モデルの定義。

2. 📥 各記号の意味(記号 → 日本語)

3. 🧠 言葉での全体読み — 物語として

この式は「料理のレシピ」だと思うと分かりやすい。 完成品(y、 たとえば死亡率)は、 ベースの量 β₀ から始めて、 素材ごとの分量 × 単価 β₁x₁、 β₂x₂、 … を足していくと組み上がる。 ただし、 どんなレシピも「実際に作るとちょっとズレる」。 そのズレが ε(誤差)です。

単回帰との 決定的な違いは、 β₁ が「単独で x₁ と y を見たときの傾き」ではないこと。 重回帰の β₁ は 「他の説明変数 x₂, x₃, …, xₚ を全部一定にした上で」の x₁ の効果。 これを partial(部分・偏)と呼ぶ理由がここにあります。 「高齢化率を一定にしたうえでの、 求人倍率の追加効果」— これは単純な相関よりも はるかに政策的に意味のある情報です。

直感的に 「補正後の効果」「条件付きの効果」 と読むと、 統計を知らない人にも伝わります。 「同じ高齢化率の県同士で比べたとき、 求人倍率の高い県の方が死亡率が 0.18 ポイント低い」のように。 これは Frisch-Waugh-Lovell の定理として正式に証明されており、 「重回帰の β₁ は、 (a) x₁ を他の x で回帰した残差と (b) y を他の x で回帰した残差で、 単回帰した時の傾きと一致する」という美しい性質を持つ。

4. 🔍 推定の「気持ち」 — OLS は何を最小化しているか

公式 $\hat{\boldsymbol{\beta}} = (X^\top X)^{-1} X^\top \mathbf{y}$ は天から降ってきたものではありません。 「残差二乗和 RSS = Σ(y − ŷ)² を最小化する β を解析的に解いた結果」です。 RSS を β で微分してゼロと置くと、 正規方程式(normal equations) $X^\top X \hat{\boldsymbol{\beta}} = X^\top \mathbf{y}$ が出てくる。 これを左から $(X^\top X)^{-1}$ で掛けると公式が得られる。

なぜ「二乗和」を最小化するのか? — 大きく 3 つの理由があります:

  1. 正規分布のもとで最尤と一致:ε が独立同分布の正規 N(0, σ²) に従うとき、 OLS 推定量は最尤推定量(MLE)と完全に一致する。
  2. BLUE 性質:ガウス・マルコフ仮定(線形・独立・等分散)のもとで、 線形不偏推定量の中で 分散最小。 これが「OLS は最良(best)」と呼ばれる根拠。
  3. 計算が解析的に閉じる:絶対値の和を最小化する LAD(最小絶対偏差回帰)と違い、 OLS は 1 ステップで解ける(逆行列計算)。 数値最適化が要らない。

5. 💬 仮定が崩れるとき — 何が起こるか

重回帰の「美しい性質」(BLUE)は、 ガウス・マルコフ仮定 が成り立つ前提です。 仮定とは:

仮定が破れた時の症状と対処:

崩れた仮定症状対処
等分散性SE が間違う、 t/p 値が嘘になるHC3 ロバスト SE、 WLS
独立性(時系列)SE が過小、 自己相関残差Newey-West SE、 ARIMA、 パネル固定効果
多重共線性β SE 爆発、 符号反転変数除外、 Ridge、 Lasso、 PCA
外生性違反β̂ にバイアス、 因果解釈不可操作変数法(IV)、 DiD、 RDD
非線形残差プロットに曲線パターン多項式項、 GAM、 ツリーモデル

つまり「数式を読む」とは、 単に記号を翻訳することではなく、 仮定が破れた時に何が壊れるか/どう直すかまで含めて理解すること。 ここまで読めて初めて、 重回帰を「使える」と言えます。

6. 📚 因果推論との橋渡し — βⱼ は「効果」か?

最後に重要な注意:βⱼ は 「効果」ではなく「条件付き連関」に過ぎません。 観察データから因果を読みたければ、 (i) 処置の無作為割り付け(RCT)、 または (ii) 適切な操作変数(IV)、 (iii) 差の差(DiD)、 (iv) 不連続回帰(RDD) のいずれかが必要。 「重回帰の係数」を「政策効果」と短絡しないのが、 統計リテラシーの第一歩です。

それでも観察研究で重回帰を使う価値はあります: (a) 予測モデルとして 高精度なら十分実用的、 (b) 仮説生成として どの変数が「効きそう」かを絞り込める、 (c) 政策シミュレーションの第一段階として 大まかな見当をつける、 などの用途で必要不可欠です。

🧮 実値で計算してみる — SSDSE-B-2026

SSDSE-B-2026 から 47 都道府県の死亡率系指標を 4 変数で重回帰した実例で、 数値を追います。 ここでは目的変数として 死亡数(A4200)を 総人口(A1101)で割った千分率を「死亡率」とし、 説明変数は高齢化率・病院数・新規求職申込件数・年平均気温の 4 つを取ります。

📥 使うデータ列(コード対応表)

役割列コード名称派生処理
yA4200 / A1101 × 1000死亡率(千分率)死亡数 ÷ 人口 ×1000
x₁A1303 / A1101 × 100高齢化率(%)65歳以上人口 ÷ 総人口
x₂I510120 / A1101 × 10000010万人あたり病院数正規化
x₃F3101新規求職申込件数(件)そのまま
x₄B4101年平均気温(℃)そのまま

🧮 1. 記述統計(n=47)

変数平均標準偏差最小最大
y:死亡率(‰)14.12.19.7(東京)19.2(秋田)
x₁:高齢化率(%)31.63.322.8(東京)39.1(秋田)
x₂:人口10万あたり病院6.92.83.1(神奈川)16.1(高知)
x₃:新規求職申込件数(件)60,40453,02015,329(鳥取)270,954(東京)
x₄:年平均気温(℃)16.82.011.0(北海道)23.8(沖縄)

🧮 2. 単相関(y との r)

x₁: r = +0.972(強い正)/x₂: r = +0.52/x₃: r = −0.59/x₄: r = −0.44。 「高齢化率 1 変数で死亡率はほぼ説明できる」が見えます。 では他の 3 変数は「ついで」なのか? 重回帰で検証します。

🧮 3. OLS 推定結果(4 変数)

変数β̂SEt値p値標準化 β*VIF
切片 β₀-6.802.08-3.260.002
β₁(高齢化率)+0.6490.04614.09<0.001+1.024.31
β₂(病院数)-0.0100.038-0.250.804-0.012.07
β₃(新規求職申込件数)+2.9e-062.0e-061.430.161+0.072.10
β₄(気温)+0.0170.0520.330.743+0.022.09

R² = 0.947、 調整済み R² = 0.943、 F(4, 42) = 189.5、 p < 0.001。 モデル全体は強く有意。

核心の解釈: 単相関で目立った x₄(気温、 r=−0.44)も、 高齢化率を制御すると独立効果はほぼ消える(β₄ = +0.017, p=0.74)。 これは 「寒冷地は高齢化が進む」という第3変数 x₁ が真犯人だったことを意味する。 病院数 x₂ も同様で、 「病院数が多い県ほど死亡率が高い」のは、 「高齢者の多い県に病院が多い」という逆向きの因果関係(または共通要因)で説明される疑似相関。

🧮 4. 沖縄県を代入して予測

沖縄県:x₁=23.8, x₂=5.2, x₃=43877, x₄=23.8 →

$\hat{y} = -6.80 + 0.649 \times 23.8 - 0.010 \times 5.2 + 2.9\text{e-}6 \times 43877 + 0.017 \times 23.8$

= -6.80 + 15.45 − 0.05 + 0.13 + 0.40 = 9.13(‰)。 実測値 ≈ 10.29。 残差 = +1.16。 やや過小予測。

🧮 5. 秋田県を代入して予測

秋田県:x₁=39.1, x₂=5.3, x₃=30175, x₄=13.7 →

$\hat{y} = -6.80 + 0.649 \times 39.1 - 0.010 \times 5.3 + 2.9\text{e-}6 \times 30175 + 0.017 \times 13.7$

= -6.80 + 25.38 − 0.05 + 0.09 + 0.23 = 18.85(‰)。 実測値 ≈ 19.17。 残差 = +0.32。 高精度。

🧮 6. F検定の手計算

R² = 0.947, p = 4, n = 47 →

$F = \frac{R^2 / p}{(1-R^2) / (n-p-1)} = \frac{0.947/4}{0.053/42} = \frac{0.2369}{0.00125} \approx 189.5$

自由度 (4, 42) で F の 1% 臨界値は約 3.83、 0.1% 臨界値は約 5.85。 190 >> 5.85 → p < 0.001、 モデル全体は極めて強く有意。

🧮 7. VIF の手計算(β₁ の場合)

x₁(高齢化率)を x₂, x₃, x₄ で回帰 → R²₁ ≈ 0.77。 よって VIF₁ = 1/(1−0.77) ≈ 4.31。 5 を下回るので多重共線性は許容範囲。

🧮 8. 標準化 β の意味

β₁* = +1.02 は「高齢化率を 1 標準偏差(3.3 ポイント)動かすと、 死亡率が 1.02 標準偏差(≈ 2.14 ‰)動く」。 他の 3 変数の標準化 β は |0.01|, |0.07|, |0.02| なので、 高齢化率の 影響度は他を 15 倍以上上回る。 重回帰の標準化 β は、 政策議論で「どの要因が一番効くか」を語る最重要数値です。

🧮 9. 残差の正規性検定(Shapiro-Wilk)

残差の正規性を Shapiro-Wilk 検定で確認すると W = 0.946、 p = 0.030(n=47)。 0.05 をわずかに下回り、 帰無仮説「正規分布である」は 5% 水準で辛うじて棄却される(残差正規性はやや弱い)。 → t検定・F検定は大標本では頑健だが、 厳密には HC3 ロバスト SE の併用が無難

🧮 10. 等分散性検定(Breusch-Pagan)

BP 統計量 = 1.82、 自由度 4 のカイ二乗で p = 0.77。 等分散性の帰無仮説は棄却されず、 通常の OLS SE をそのまま信頼できる。

🧮 数式に値を入れて手で計算する: 重回帰係数

合成 5 サンプル 2 説明変数で重回帰係数を計算する。

Step 1: データ

x1x2y
125
214
339
429
5414

Step 2: 正規方程式 β = (XᵀX)⁻¹Xᵀy

β = [β0, β1, β2] ≈ [-0.40, 1.356, 1.889] モデル: y = -0.40 + 1.356·x1 + 1.889·x2

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
from sklearn.linear_model import LinearRegression
X = np.array([[1,2],[2,1],[3,3],[4,2],[5,4]])
y = np.array([5,4,9,9,14])
m = LinearRegression().fit(X, y)
print(f"切片: {m.intercept_:.2f}")
print(f"係数: {m.coef_}")

📤 実行結果

切片: -0.40 係数: [1.35555556 1.88888889]

💬 手計算 (Step 2) [-0.40, 1.356, 1.889] と Python 出力が完全一致。

🐍 Python 実装

① statsmodels — 統計学的に正統な分析

🎯 目的

SSDSE-B-2026 を読み込み、 4 変数で重回帰を実施し p値・SE・F検定・VIF を一括取得する。 統計推論が目的のときの第一選択。

📥 入力

data/raw/SSDSE-B-2026.csvcp932 でロード。 2023 年データに絞り、 派生変数(高齢化率 %、 10万人あたり病院数)を作成。

📤 出力

係数 β̂、 SE、 t値、 p値、 95%CI、 R²、 調整 R²、 F値、 各変数の VIF、 Breusch-Pagan 統計量、 Durbin-Watson、 残差プロット用配列。

💬 解釈

β₁(高齢化率)が圧倒的に有意(t=14.09, p<.001)、 他の 3 変数は単相関では見えても重回帰では非有意。 VIF max=4.31 で共線性問題なし。

 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
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.stats.diagnostic import het_breuschpagan

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026']==2023].dropna(subset=['A4200','A1101','A1303','I510120','F3101','B4101']).copy()

# 派生変数
df['death_rate']    = df['A4200']    / df['A1101'] * 1000   # 千分率
df['aging_rate']    = df['A1303']    / df['A1101'] * 100    # %
df['hosp_per_100k'] = df['I510120']  / df['A1101'] * 100000

formula = 'death_rate ~ aging_rate + hosp_per_100k + F3101 + B4101'
model = smf.ols(formula, data=df).fit()
print(model.summary())

# VIF
X = sm.add_constant(df[['aging_rate','hosp_per_100k','F3101','B4101']])
vif = {c: variance_inflation_factor(X.values, i) for i, c in enumerate(X.columns)}
print('VIF:', {k: round(v, 2) for k, v in vif.items()})

# 等分散検定(Breusch-Pagan)
bp_stat, bp_p, _, _ = het_breuschpagan(model.resid, model.model.exog)
print(f'Breusch-Pagan p = {bp_p:.3f}  ({"等分散OK" if bp_p>0.05 else "ヘテロ"})')

# ロバスト標準誤差(HC3)
robust = smf.ols(formula, data=df).fit(cov_type='HC3')
print(robust.summary())

② scikit-learn — 予測重視 + Pipeline + 交差検証

🎯 目的

同じデータで「OLS」「Ridge」「Lasso」「Elastic Net」を Pipeline 化し、 5-fold 交差検証で汎化性能を比較する。

📥 入力

NumPy 配列の X (47, 4) と y (47,)。 KFold(shuffle=True, random_state=42)。 StandardScaler で z 標準化済み。

📤 出力

各モデルの CV R² 平均±SD、 標準化済み係数、 Lasso による自動変数選択結果(係数 0 になった変数)。

💬 解釈

共線性が軽度のため OLS と Ridge はほぼ同等。 Lasso は β₂, β₃, β₄ を 0 に縮める → 「実質的に高齢化率 1 変数モデル」と等価。 これが Lasso の特徴。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score, KFold
import numpy as np

X = df[['aging_rate','hosp_per_100k','F3101','B4101']].values
y = df['death_rate'].values
cv = KFold(5, shuffle=True, random_state=42)

models = {
    'OLS'   : LinearRegression(),
    'Ridge' : Ridge(alpha=1.0),
    'Lasso' : Lasso(alpha=0.1, max_iter=10000),
    'EN'    : ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=10000),
}
for name, est in models.items():
    pipe = Pipeline([('sc', StandardScaler()), ('m', est)])
    scores = cross_val_score(pipe, X, y, cv=cv, scoring='r2')
    pipe.fit(X, y)
    coefs = pipe['m'].coef_
    print(f'{name:6s}  CV R² = {scores.mean():.3f} ± {scores.std():.3f}   '
          f'coef = {dict(zip(["aging","hosp","jobs","temp"], coefs.round(3)))}')

③ numpy / scipy — 行列で「手計算」(理解の最終段階)

🎯 目的

$\hat{\beta} = (X^\top X)^{-1} X^\top y$ を行列演算で直接計算し、 SE・t値・p値・95%CI を組み立てる。

📥 入力

切片列を追加した設計行列 X_design (47, 5)、 目的変数 y (47,)。 残差から $\hat{\sigma}^2$ を計算。

📤 出力

β̂、 残差、 σ²、 Cov(β̂)、 SE、 t値、 p値、 95%CI。 statsmodels と完全一致を確認。

💬 解釈

「公式は天から降ってきたものではなく、 13 行のコードで導出できる」と腑に落ちる。 教育的な価値の高いコード。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import numpy as np
from scipy import stats, linalg

X_design = np.column_stack([np.ones(len(df)), X])           # (n, p+1)
beta = linalg.solve(X_design.T @ X_design, X_design.T @ y)  # β̂
residuals = y - X_design @ beta
n, p = X_design.shape
sigma2 = (residuals @ residuals) / (n - p)
cov_beta = sigma2 * linalg.inv(X_design.T @ X_design)
se = np.sqrt(np.diag(cov_beta))
t_stats = beta / se
p_values = 2 * (1 - stats.t.cdf(np.abs(t_stats), df=n-p))
t_crit = stats.t.ppf(0.975, df=n-p)
ci_low  = beta - t_crit * se
ci_high = beta + t_crit * se

names = ['intercept', 'aging', 'hosp', 'jobs', 'temp']
for nm, b, s, t_, p_, lo, hi in zip(names, beta, se, t_stats, p_values, ci_low, ci_high):
    print(f'{nm:10s}  β={b:8.3f}  SE={s:.3f}  t={t_:6.2f}  p={p_:.4f}  95%CI=[{lo:.2f}, {hi:.2f}]')

④ matplotlib — 残差プロットによる仮定診断

🎯 目的

残差 vs 予測値、 残差 QQ プロット、 影響度(Cook's distance)を可視化し、 線形性・等分散性・正規性・外れ値を診断する。

📥 入力

statsmodels モデルの residuals, fittedvalues, get_influence() 結果。

📤 出力

4 つのサブプロット:残差散布図、 QQ プロット、 スケール-ロケーション図、 leverage vs Cook's distance。

💬 解釈

残差がランダム雲ならOK。 ファン状(広がり)なら等分散性違反、 曲線パターンなら非線形効果が残っている。 Cook's d > 4/n の点は影響大。

 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 matplotlib.pyplot as plt
from scipy import stats as sst
import numpy as np

fig, ax = plt.subplots(2, 2, figsize=(12, 10))

# 残差 vs 予測値
ax[0,0].scatter(model.fittedvalues, model.resid)
ax[0,0].axhline(0, color='red', ls='--')
ax[0,0].set(xlabel='Fitted', ylabel='Residual', title='Residuals vs Fitted')

# QQ プロット
sst.probplot(model.resid, dist='norm', plot=ax[0,1])

# スケール - ロケーション
ax[1,0].scatter(model.fittedvalues, np.sqrt(np.abs(model.get_influence().resid_studentized_internal)))
ax[1,0].set(xlabel='Fitted', ylabel='√|Std. Residual|', title='Scale-Location')

# Cook's distance
infl = model.get_influence()
cooks = infl.cooks_distance[0]
ax[1,1].stem(range(len(cooks)), cooks)
ax[1,1].axhline(4/len(df), color='red', ls='--', label='4/n')
ax[1,1].set(xlabel='Observation', ylabel="Cook's D", title="Cook's distance")
ax[1,1].legend()
plt.tight_layout(); plt.savefig('mreg_diagnostics.png', dpi=120)

⑤ scikit-learn — Lasso のパス(係数の縮約過程)

🎯 目的

正則化強度 α を変化させながら Lasso 係数の軌跡をプロットし、 「どの変数が最後まで残るか」を可視化する。

📥 入力

標準化済み X (47,4)、 y (47,)、 α のグリッド [0.001, 0.01, 0.1, 1.0, 10]。

📤 出力

各 α における 4 個の係数(aging/hosp/jobs/temp)の値。 横軸 log(α)、 縦軸 β。

💬 解釈

α を上げるほど β は 0 に縮む。 一番粘るのが β₁(高齢化率)。 これは「予測に最も寄与する変数」と一致する。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from sklearn.linear_model import lasso_path
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler

X_std = StandardScaler().fit_transform(X)
alphas, coefs, _ = lasso_path(X_std, y, alphas=[0.001, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0])
print('coefs shape:', coefs.shape)  # (4, len(alphas))

for i, name in enumerate(['aging','hosp','jobs','temp']):
    plt.plot(-np.log10(alphas), coefs[i], label=name)
plt.legend(); plt.xlabel('-log10(α)'); plt.ylabel('β'); plt.title('Lasso path')
plt.savefig('lasso_path.png', dpi=120)

⑥ statsmodels — 部分回帰プロット(CCPR)

🎯 目的

各説明変数の「他変数を制御した後の x_j vs y の関係」を視覚化し、 偏回帰係数の意味を直感的に理解する。

📥 入力

statsmodels モデルオブジェクト、 説明変数の名前リスト。

📤 出力

4 つの partial regression plot。 x 軸は「x_j を他で回帰した残差」、 y 軸は「y を他で回帰した残差」。

💬 解釈

各プロットの傾きが偏回帰係数 βⱼ。 aging_rate の傾きが急で、 ほぼ単相関と同じ傾向。 他は傾き ≈ 0 で「制御後は効果なし」が明示される。

1
2
3
import statsmodels.api as sm
fig = sm.graphics.plot_partregress_grid(model)
fig.tight_layout(); fig.savefig('partregress.png', dpi=120)

⑦ statsmodels — モデル比較(AIC / BIC)と階層的 F検定

🎯 目的

変数を追加するごとに AIC, BIC, 調整済み R² を比較し、 「どこまで変数を入れる価値があるか」を判断。 入れ子モデルの F検定も実施。

📥 入力

3 つの入れ子モデル:M1=高齢化率のみ、 M2=高齢化率+病院数、 M3=高齢化率+病院数+新規求職申込件数+気温。

📤 出力

各モデルの AIC, BIC, 調整済み R², F検定の p値。

💬 解釈

M1→M2 で AIC は微減(病院数は限界的な貢献)。 M2→M3 では BIC が増加 → 「BIC 基準では M2 が最適」。 これが ペナルティ込みのモデル選択

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
m1 = smf.ols('death_rate ~ aging_rate', data=df).fit()
m2 = smf.ols('death_rate ~ aging_rate + hosp_per_100k', data=df).fit()
m3 = smf.ols('death_rate ~ aging_rate + hosp_per_100k + F3101 + B4101', data=df).fit()

print(f'{"Model":<6}{"AIC":>8}{"BIC":>8}{"adjR2":>8}')
for name, m in [('M1', m1), ('M2', m2), ('M3', m3)]:
    print(f'{name:<6}{m.aic:8.2f}{m.bic:8.2f}{m.rsquared_adj:8.3f}')

from statsmodels.stats.anova import anova_lm
print(anova_lm(m2, m3))

⑧ scipy / sklearn — Huber 損失でロバスト推定

🎯 目的

外れ値に強い HuberRegressor を試し、 通常の OLS と係数を比較。 影響度の高い県を除いても結果が安定するか確認。

📥 入力

X (47, 4), y (47,), StandardScaler 適用済み。 ε = 1.35(Huber の閾値)。

📤 出力

Huber と OLS の係数比較。 大きく違う変数があれば外れ値の影響を受けていた証拠。

💬 解釈

沖縄県は気温・高齢化率・死亡率すべて極端値。 OLS では沖縄に引きずられる傾向あり。 Huber の方が他県平均的な傾向を反映する。

1
2
3
4
from sklearn.linear_model import HuberRegressor
huber = HuberRegressor(epsilon=1.35)
huber.fit(StandardScaler().fit_transform(X), y)
print('Huber β:', dict(zip(['aging','hosp','jobs','temp'], huber.coef_.round(3))))

⑨ patsy で柔軟な式表記

🎯 目的

R 流の式(formula)記法で、 交互作用項・多項式項・離散化を柔軟に組み込む。

📥 入力

pandas DataFrame と R 風の式 'y ~ x1 + I(x1**2) + x1:x2 + C(region)'。

📤 出力

Design matrix が自動生成され、 statsmodels に渡せる形式に。

💬 解釈

多項式 I(x**2)、 交互作用 x1:x2、 カテゴリ C(...) を 1 行で表現。 学術論文の式そのまま書ける。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# ── この抜粋で使うデータを用意します ──
import patsy
import statsmodels.api as sm
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 年の 47 都道府県
df = df.copy()
df['death_rate']    = df['A4200'] / df['A1101'] * 1000     # 人口千人あたり死亡数
df['aging_rate']    = df['A1303'] / df['A1101'] * 100      # 高齢化率 %
df['hosp_per_100k'] = df['I510120'] / df['A1101'] * 100000 # 人口 10 万人あたり一般病院数
_E = {'北海道': '北', '青森県': '北', '岩手県': '北', '宮城県': '北', '秋田県': '北',
      '山形県': '北', '福島県': '北'}
df['region'] = df['Prefecture'].map(_E).fillna('その他')   # 2 水準の地域区分

y, X = patsy.dmatrices('death_rate ~ aging_rate + I(aging_rate**2) + aging_rate:hosp_per_100k + C(region)', data=df, return_type='dataframe')
model = sm.OLS(y, X).fit()
print(model.summary())

⑩ Bootstrap で頑健な信頼区間

🎯 目的

正規性仮定が怪しい時、 Bootstrap で経験的に信頼区間を推定する。 ノンパラメトリック法。

📥 入力

X (n, p), y (n,), n_boot = 1000 回のリサンプリング。

📤 出力

各係数の bootstrap 分布、 percentile 95%CI。

💬 解釈

正規分布仮定の 95%CI とほぼ一致すれば仮定 OK。 違いが大きければ Bootstrap CI の方を採用する。

 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
# ── この抜粋で使うデータを用意します ──
import numpy as np
import pandas as pd
from sklearn.utils import resample

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 2023 年の 47 都道府県
# const を除く 4 変数(下の name 一覧と数を合わせる)
X = np.column_stack([
    df['A1303'] / df['A1101'] * 100,          # aging: 高齢化率
    df['I510120'] / df['A1101'] * 100000,     # hosp : 人口10万人あたり一般病院数
    df['F3103'].astype(float) / 1000,         # jobs : 月間有効求人数(千件)
    df['B4101'].astype(float)])               # temp : 年平均気温
y = (df['A4200'] / df['A1101'] * 1000).to_numpy(dtype=float)   # 死亡率


n_boot = 1000
betas = []
for _ in range(n_boot):
    Xb, yb = resample(X, y, random_state=None)
    Xd = np.column_stack([np.ones(len(Xb)), Xb])
    beta_b = np.linalg.lstsq(Xd, yb, rcond=None)[0]
    betas.append(beta_b)
betas = np.array(betas)

ci_low  = np.percentile(betas, 2.5, axis=0)
ci_high = np.percentile(betas, 97.5, axis=0)
print('Bootstrap 95%CI:')
for i, name in enumerate(['const','aging','hosp','jobs','temp']):
    print(f'  {name}: [{ci_low[i]:.3f}, {ci_high[i]:.3f}]')

⚠️ 落とし穴 — よくある失敗 5 件

多重共線性で係数が暴れる
説明変数同士の相関が 0.95 を超えると、 β̂ⱼ の SE が爆発し、 個別 t検定では誰も有意でないのに F検定では全体有意、 という奇妙な症状が出る。 VIF を必ず計算し、 > 10 の変数は除外するか、 Ridge/Lasso/PCA を導入する。 「F が大、 t が小」は警告。
サンプル不足での過学習
n=47 で説明変数 10 個を入れると、 R² は見かけ上 0.9 を超えるのに調整済み R² は急落、 5-fold CV R² は負値、 という典型的な過学習に陥る。 経験則 n ≥ 10p を厳守。 ドメイン知識で変数を最小限に絞ること。
外れ値で β が大きく動く
OLS は二乗和を最小化するため、 影響度の高い 1 点に係数が引きずられる。 必ず Cook's distance / DFFITS / leverage を確認し、 影響大きい点はロバスト回帰(HuberRegressor、 quantile_regression)に切り替える。 「外れ値を削除して再計算」前に、 まず原因を調査する。
単位の違いで生 β を比較してしまう
x₁ が「億円」、 x₂ が「人」だと、 生の β̂ を見ても大小比較は無意味。 必ず標準化 β*(全変数を z 標準化してから推定)で比較する。 sklearn なら StandardScaler() + LinearRegression() で係数が直接 β* になる。
p値だけ見て解釈する
n=47 と n=10,000 では同じ係数でも p値が大きく違う。 n が大きければ実質ゼロの効果でも p<.05 になる。 効果量(標準化β)、 95%CI、 実用的な意味を併記すべき。 「p<.05 だから重要」は短絡。

⚠️ 落とし穴 深掘り 10 項目

基本の落とし穴 5 つは押さえたとして、 さらに見落としがちな「重回帰の罠」を 10 個追加します。

1. シンプソンのパラドックス

サブグループでは正の効果なのに、 全体で集計すると負になる現象。 重回帰でも「グループダミーを入れ忘れる」と、 サブグループの符号と全体の符号が逆転することがある。 必ずグループ別の散布図を確認すること。

2. 範囲外への外挿

学習データの x の範囲外に予測を伸ばすと、 線形の仮定が崩れる可能性大。 「築 50 年のマンション」のデータで学習して「築 0 年」を予測すると、 切片次第で異常値が出る。 必ず学習データの分布範囲を確認。

3. ベルクソンバイアス

「両方の条件を満たすサンプル」のみ集めて回帰すると、 元集団には存在しない相関が出る。 病院に来た患者だけで「症状 A と病気 B の関係」を見ると、 母集団とは異なる結論になることがある。

4. 選択バイアス

アンケート回答者だけのデータで回帰すると、 「回答しなかった人」の特性が抜ける。 ヘックマンの 2 段階推定で補正可能だが、 手間がかかる。

5. 測定誤差バイアス(attenuation bias)

説明変数 x に測定誤差があると、 β̂ は 0 方向に縮む(attenuation)。 教育年数の自己申告など、 報告誤差のある変数では β を過小評価しがち。 構造方程式モデル (SEM) や IV で補正可能。

6. 逆因果(reverse causality)

y → x の因果関係が逆方向にも存在すると、 β̂ は混合された値になる。 「病院数→死亡率」の効果と「死亡率→病院数」(高齢化地域に病院誘致)の効果が同時に動く。 時間ラグを取る、 操作変数を使う、 で対処。

7. 欠損データの非ランダム性

欠損が「ランダムに起きる」(MCAR) ではなく、 「ある条件で起きる」(MAR / MNAR) と、 単純な listwise deletion でバイアス。 多重代入や逆確率重み付けで補正。

8. 多重比較問題

20 個の変数を入れて 1 個でも p<.05 になれば、 偶然の可能性が高い(family-wise error rate)。 Bonferroni、 Holm、 FDR で補正する。 重回帰では「F検定が有意か」を最初にチェック。

9. データのリーク

説明変数に「未来の情報」「目的変数の派生」が紛れると、 R² が見かけ上跳ね上がる。 たとえば「翌月の売上を当月の利益で予測」など。 時系列ではデータ分割の時点を厳密に管理。

10. 因果のはしごの誤読

Judea Pearl の「因果のはしご」では、 (1) 連関 → (2) 介入 → (3) 反実仮想、 の 3 段階。 重回帰は (1) のレベル。 介入効果を語るには (2)、 「もし〜だったら」を語るには (3) が必要。 重回帰 1 本で「効果」「介入」「反実仮想」を語ると論理破綻する。

🗺 概念マップ

重回帰を中心に、 上位・下位・並列の概念をツリーで配置:

一般化線形モデル (GLM)
├─ 線形回帰
│  ├─ 単回帰 (p=1)
│  ├─ 重回帰 (p≥2)  ← この用語
│  │  ├─ OLS                — 通常の最小二乗
│  │  ├─ WLS / GLS          — 重み付き/一般化最小二乗
│  │  ├─ Ridge              — L2 正則化
│  │  ├─ Lasso              — L1 正則化
│  │  └─ Elastic Net        — L1+L2
│  └─ 多項式回帰
├─ ロジスティック回帰 (二値)
├─ ポアソン回帰 (カウント)
└─ プロビット回帰

拡張:
├─ GAM (非線形)
├─ 混合効果モデル (階層構造)
├─ パネル固定効果モデル
└─ Bayesian Regression

仮定が崩れた時:
├─ HC3 / Newey-West SE (等分散・自己相関違反)
├─ ロバスト回帰 (外れ値)
├─ 操作変数法 IV (内生性)
└─ DiD / RDD (因果推論)

📜 歴史と系譜 — どうしてこの概念が生まれたか

重回帰の系譜は意外に古く、 18 世紀の天文学にまで遡ります。

1. 1805: ルジャンドル — 最小二乗法の発明

アドリアン=マリ・ルジャンドルが彗星軌道計算のために 最小二乗法(Méthode des moindres carrés)を発表(1805 年)。 同時期にガウスも独立に発見しており、 「ガウス・ルジャンドル法」とも呼ばれる。 これが重回帰の数学的基礎。

2. 1885-1900: ゴルトン → ピアソン → ユール

フランシス・ゴルトン(1885)が遺伝学の研究で 「回帰(regression to the mean)」という言葉を作る。 カール・ピアソン(1896)が相関係数を厳密化し、 ユードニー・ユール(1907)が 偏回帰係数(partial regression coefficient)を定義。 ここで現代的な重回帰の枠組みが完成。

3. 1930s-50s: フィッシャー、 マルコフ — 推論の理論化

R.A. フィッシャー(1922)が最尤法と F検定を確立。 ガウス・マルコフの定理が整理され、 OLS の BLUE 性質が証明される。 これにより重回帰は「推定 + 検定」の体系として完成した。

4. 1960s-80s: 計量経済学の興隆

クリーヴ・グレンジャー、 ジェームズ・ヘックマンらにより、 内生性・自己相関・パネルデータといった現実データの問題点が体系化される。 ヘックマンの選択バイアス補正、 ヘンドリーの一般から特殊への方法論など、 計量経済学の現代スタイルが確立。

5. 1996: Lasso と正則化革命

ロバート・ティブシラニが Lasso(L1 正則化)を発表(Tibshirani, 1996)。 高次元データへの対応と自動変数選択を可能にし、 機械学習との接続が始まる。 Elastic Net(Zou & Hastie, 2005)で完成形。

6. 2000s-現在: 機械学習との融合

scikit-learn、 statsmodels、 PyMC、 Stan などの普及で、 重回帰は 「全データサイエンティストの基本ツール」になった。 SHAP・LIME などの解釈ツールで非線形モデルにも「βⱼ 的解釈」が広がる。 重回帰は古いが、 現代でも色褪せない。

7. 日本での発展

日本では 1950 年代に統計数理研究所の北川敏男・赤池弘次らが計量経済学を発展させ、 AIC(赤池情報量基準、 1973)を生み出した。 これは重回帰のモデル選択における世界標準。 SSDSE データを使う本コンペは、 こうした半世紀以上の伝統の上に立つ。

🔧 実装詳細 — 一段深く潜る

「OLS で重回帰」と一言で言っても、 実装にはいくつもの細部があります。 ここでは「教科書には載りにくいが現場で大事」な詳細を整理します。

1. 数値計算の安定性 — なぜ linalg.solve を使うべきか

公式 $\hat{\beta} = (X^\top X)^{-1} X^\top y$ をそのままコード化するなら inv(X.T @ X) @ X.T @ y ですが、 これは数値的に不安定。 条件数(condition number)が大きいと丸め誤差で結果が壊れる。 推奨は:

2. ダミー変数の扱い — pandas の get_dummies

カテゴリ変数(地域、 業種など)は one-hot エンコーディングで扱う。 ただし注意:

3. 交互作用項(interaction term)の入れ方

「x₁ の効果が x₂ の値で変わる」場合、 x₁ × x₂ を新変数として追加する。 statsmodels では 'y ~ x1 * x2' が自動で x1, x2, x1:x2 の 3 つを入れてくれる。 解釈は:

4. 多項式項 — 非線形を線形モデルで扱う

y と x₁ の関係が U 字なら、 x₁ + x₁² を入れる。 経験年数と賃金の Mincer 型回帰など、 経済学で頻出。 注意:x₁ と x₁² は強く相関するので 中心化(x₁ − x̄)してから二乗するのが推奨。 sklearn なら PolynomialFeatures

5. 重み付き OLS(WLS)

観測値ごとに信頼度が違うときは重み w_i を付ける。 重みは「分散の逆数」が理論的に最適:$w_i = 1 / \sigma_i^2$。 ヘテロスケダスティシティへの対処にも使える。 smf.wls(formula, data=df, weights=w)

6. 欠損値の扱い

SSDSE データは時々欠損があります。 基本戦略:

7. 標準誤差の選択肢一覧

タイプstatsmodels の指定適用場面
古典的 (homoscedastic)cov_type='nonrobust'等分散仮定OK
HC0 (White)cov_type='HC0'ヘテロ性ありの基本
HC1cov_type='HC1'小標本補正
HC2cov_type='HC2'leverage 補正
HC3cov_type='HC3'小標本・leverage 両方(推奨)
クラスタロバストcov_type='cluster'パネル・階層データ
Newey-Westcov_type='HAC'時系列・自己相関

⚙️ 運用とトラブルシュート

本番運用での「壊れ方」と対処を整理。

1. データドリフトの監視

学習時と本番で説明変数の分布が変わると β̂ は陳腐化する。 月次で平均・分散・KL ダイバージェンスを取り、 5% を超える変動があれば再学習。 不動産モデルなら「平均坪単価のシフト」、 マーケティングなら「広告予算の構成変化」が典型。

2. モデル劣化の検知

予測誤差(RMSE、 MAE)を本番ログから継続計算。 学習時の CV RMSE の 1.5 倍を超えたらアラート。 また、 残差の分布が変化していないか、 自動 KS 検定で確認する。

3. パイプライン障害

SSDSE データのような外部データを毎年更新するパイプラインは、 列名変更・コードの追加・欠損値の増加で簡単に壊れる。 schema validation(great_expectations、 pandera)を組み込み、 異常があれば deploy をブロックする。

4. 外れ値の混入

単一の外れ値で β が大きく動くことを利用したデータポイズニング攻撃が存在する。 本番では Z-score > 5 の入力を自動拒否、 IQR ベースのチェックを通すなど、 入力検証を厳密に。

5. レスポンス時間と再学習頻度

用途予測 latency 要求再学習頻度典型構成
バッチ予測(夜間)数十分OK週1〜月1Airflow + S3
オンライン API< 100 ms週1FastAPI + Redis
広告入札< 10 ms時間単位C++/Rust 実装
バックテスト数時間OK変更時のみJupyter

6. ロールバック戦略

モデルは MLflow などで version 管理し、 旧版に即座に戻せるようにする。 A/B テストで段階リリース。 「全トラフィックの 1% で新モデル → 異常がなければ 10% → 50% → 100%」のカナリアリリース。

💴 コストと見積もり

重回帰そのものの計算コストは極めて低い(O(np² + p³))が、 周辺コストが実は本番では支配的。

1. 学習コスト

データ規模計算時間必要メモリコスト目安
n=47, p=5 (本コンペ規模)< 0.01 秒数 MBローカル PC で十分
n=10,000, p=50< 1 秒数十 MBローカル可
n=1,000,000, p=100数秒数 GBローカル可
n=100,000,000, p=1000数分〜数十分数百 GBSpark MLlib / クラウド

2. 推論コスト

予測は p+1 個の乗算と足し算だけ。 1 件 ≈ ナノ秒オーダー。 1 億件でも 1 秒以内で処理可能。 これが「重回帰はインフラ的に楽」と言われる所以。

3. 人的コスト(隠れたボトルネック)

実は重回帰の総コストの 80% は 「データ前処理」「結果の解釈・報告」。 計算コストは無視できる。 アナリストが半日〜数日かけて:

4. 教育コスト

重回帰を「正しく」使いこなすには、 大学レベルの統計学(OLS、 仮定診断、 多重共線性、 因果推論)を最低限学ぶ必要がある。 自社で internal training を入れる場合、 1 人あたり 20〜40 時間の研修コスト(社員時給 5,000 円換算で 10〜20 万円)。 e-learning や AtCoder / KaggleでのSkill Tier 化が有効。

5. ライセンス・規制コスト

scikit-learn (BSD)、 statsmodels (BSD)、 R (GPL) など主要ツールはオープンソース無料。 SAS や SPSS の商用利用は年数十万〜数百万円。 ただし金融や医療の規制業界では SAS validation の方が監査で楽、 という現実もある。

🛡 ガバナンス・セキュリティ・コンプライアンス

重回帰モデルを業務に使う際のガバナンス論点を整理。

1. 説明責任(Accountability)

重回帰の最大の強みは 「なぜこの予測か」を係数で説明できること。 これは EU の GDPR Article 22(自動化された意思決定の説明権)にも適合する。 顧客に対して「あなたのスコアが下がった理由は、 βⱼ × xⱼ の項目 X が原因」と回答可能。

2. 公平性(Fairness)

説明変数に「性別」「人種」が入っていなくても、 「住所」「学歴」を経由して差別が忍び込む(disparate impact)。 EEOC や金融庁の公平性ガイドラインに照らし、 protected attribute での予測精度差をモニタリングする。

3. 個人情報保護

SSDSE-B-2026 は 47 都道府県の集計値なので個人情報問題は基本なし。 ただし、 マイクロデータ(個票)で重回帰する場合は匿名化(k-匿名性、 差分プライバシー)を必ず施す。 学習データの再特定化攻撃も検討事項。

4. モデルカード(Model Card)

Google が提唱するモデルカードを作成すべき項目:

5. 監査証跡(Audit Trail)

いつ・誰が・どのデータで・どの結果を出したかを完全記録。 MLflow Tracking、 DVC、 Weights & Biases などのツールで自動化。 金融や医療では Part 11(FDA)/ J-SOX 対応が必須。

6. AI ガイドラインへの対応

日本では総務省・経産省の AI 利活用ガイドライン、 政府の AI 戦略 2022。 重回帰のような「線形・透明」なモデルは high-risk 分類に該当しないことが多いが、 信用スコアや採用予測など用途次第で規制対象に。 詳細は AI ガイドライン 参照。

🏭 産業事例 6 件 — 現場ではどう使われているか

1. 医療経済:地域死亡率の要因分解

厚労省・自治体は、 47 都道府県の死亡率を高齢化率・医療体制・所得などで重回帰し、 政策の優先順位を決める。 単相関では混同される交絡を制御できるのが強み。 地域包括ケアシステムの設計や、 病床数の適正配置の議論で頻出。

2. 不動産:マンション価格の査定

築年数・面積・駅距離・階数・周辺所得など 10 数変数で価格を重回帰。 査定エンジンの古典的バックボーン。 最近は XGBoost に置き換わりつつあるが、 「なぜこの価格か」の説明用にいまも併用される。 SUUMO 等の自動査定 API の中核。

3. マーケティング:MMM(Marketing Mix Modeling)

売上を TV/デジタル/チラシ/価格/季節要因の重回帰で分解し、 媒体ごとの ROI を推定。 Facebook の Robyn、 Google の LMM が代表的 OSS。 重回帰の現代版で、 ベイズ階層・時変係数まで拡張されている。

4. 経済学:賃金関数(Mincer 型)

賃金 = β₀ + β₁ 教育年数 + β₂ 経験年数 + β₃ 経験² + ε。 半世紀来の労働経済学の主力ツール。 教育の収益率 β₁ は政策議論で必ず引用される。 Mincer (1974) 以来、 何万本もの論文がこの形を使う。

5. 製造業:品質予測モデル

鉄鋼/半導体プロセスで、 製品歩留まりを温度・圧力・時間など工程パラメータで重回帰。 SHAP と組み合わせて要因分析するのが現代的トレンド。 Six Sigma の DoE と直結。

6. 環境:大気汚染源の按分

PM2.5 濃度を気温・湿度・風速・交通量・工場排出量で重回帰し、 各汚染源の寄与率を分解。 環境省・自治体の常套ツール。 ソース・アポーションメント(source apportionment)の主要手法。

multiple regression OLS/単回帰 Ridge 回帰 Lasso 回帰 ロジスティック回帰 一般化加法モデル(GAM) パネルデータ分析

🔗 隣接手法への橋渡し

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

SSDSE-B-2026 で県民所得を被説明変数、 人口・高齢化率・就業者数を説明変数とする重回帰は、 多重共線性 (人口と就業者) と外れ値 (東京) の両方を考慮する必要がある。 標準化偏回帰係数で比較するのが安全。

🌳 意思決定ツリー — 状況別の手順

どの正則化を使うべきか? どんな診断を回すべきか? — 状況別の意思決定ツリーをまとめます。

🌳 ツリー 1: 重回帰の出発点

目的変数 y は何か?
├─ 連続値 → 重回帰(このページ)/GAM/RandomForest
├─ 二値 (0/1) → ロジスティック回帰
├─ カウント (0,1,2,...) → ポアソン回帰/負の二項回帰
├─ カテゴリ (3つ以上) → 多項ロジット
└─ 順序 (低/中/高) → 順序ロジット
    

🌳 ツリー 2: 説明変数選択

変数数 p と n の関係は?
├─ n ≥ 10p → そのまま OLS で OK
├─ n < 10p かつ予測重視 → Lasso / Elastic Net
├─ n << p (高次元) → Lasso 強推奨、 もしくは PCA + 回帰
└─ 共線性あり (VIF > 10) → Ridge / 変数除外 / PCA
    

🌳 ツリー 3: 仮定診断

残差プロットを見て:
├─ ランダム雲 → 仮定 OK
├─ ファン状の広がり → ヘテロ性、 HC3 SE か WLS
├─ 曲線パターン → 非線形効果、 多項式項か GAM
├─ 周期パターン → 自己相関、 Newey-West SE か ARIMA
└─ 一部の点だけ外れ → 外れ値、 Cook's d 確認
    

🌳 ツリー 4: 因果推論への進化

因果を主張したい?
├─ 無作為割付できる → RCT(実験)
├─ 自然実験あり → IV / DiD / RDD
├─ パネルデータあり → 固定効果モデル
├─ 観察データのみ → 「条件付き連関」までしか言えない
└─ → 報告書に「因果ではない」と明記
    

🌳 ツリー 5: モデル比較

何を基準に選ぶ?
├─ 予測重視 → 交差検証 RMSE、 CV R²
├─ 説明力重視 → 調整済み R²
├─ 「真モデル」探索 → BIC
├─ 予測 & スパース → Lasso CV
└─ 入れ子モデル比較 → F 検定 / 尤度比検定
    

📖 本コンペ過去論文での重回帰例

本コンペで再現対象となる過去論文では、 重回帰が中心的な手法として頻出します。 代表的なものを 5 件紹介します(あくまで例。 実際の論文タイトルは本リポジトリの index.html を参照)。

論文例 1: 「47 都道府県の医療費を 7 変数で説明する」

目的:医療費(人口あたり)を高齢化率・所得・病院数・医師数・気候・産業構造・教育水準で重回帰。 結果:高齢化率と医師数が主要因。 ただし医師数と医療費の因果方向に注意が必要(医療費が高いから医師が来るのか、 医師が多いから医療費が高くなるのか)。

論文例 2: 「市町村の人口減少率を 5 変数で予測する」

目的:5 年間の人口減少率を、 高齢化率・有効求人倍率・出生率・転出率・大学進学率で重回帰。 結果:転出率(特に若年層)が支配的。 政策提言:地方創生は「若年層の流出抑制」が最大のレバー。

論文例 3: 「気象データと農業生産量の重回帰」

目的:米の単収(10a あたり)を平均気温・日照時間・降水量・台風数・施肥量で重回帰。 結果:気温と日照が主要因。 ただし「気温」は二次関数的効果(暑すぎても寒すぎても減収)なので多項式項を入れた。

論文例 4: 「教育格差の地域要因分析」

目的:高校進学率を世帯所得中央値・両親学歴・教育費補助・地域人口で重回帰。 結果:所得と両親学歴が支配的。 政策提言:奨学金拡充の効果量を試算する基礎データに。

論文例 5: 「自殺率の社会経済要因」

目的:年齢調整自殺率を失業率・離婚率・所得格差・社会的孤立指標・寒冷地ダミーで重回帰。 結果:失業率と社会的孤立が主要因。 教訓:「自殺は個人の問題」ではなく社会的決定因子の影響大。 ただし因果は別途実証必要。

🏋 ワークショップ 5 ワーク — 自分で手を動かす

頭で読むだけでは身につきません。 ここでは「自分で手を動かす演習」を 5 ワーク用意します。 SSDSE-B-2026 を使う前提です。 各ワークには想定解と頻出ミスを併記しました。

📝 ワーク 1: 単純な 2 変数重回帰を組む

課題:47 都道府県の死亡率を「高齢化率」と「年平均気温」だけで重回帰せよ。 β̂、 SE、 R² を報告すること。

想定解:高齢化率の β = 0.49(p<.001)、 気温 β = −0.03(p=0.27)、 R²=0.946。 単相関では気温が r=−0.68 だが、 重回帰では非有意。 これは「寒冷地は高齢化が進む」交絡が原因。

頻出ミス:encoding を指定し忘れて文字化け(cp932 を使う)、 skiprows=[1] を忘れて 1 行目を欠損として読む。

📝 ワーク 2: 多重共線性を意図的に起こしてみる

課題:「高齢化率」と「65歳以上人口数」(A1303)を両方説明変数に入れて重回帰せよ。 VIF を計算して結果を解釈すること。

想定解:両者は定義式上 100% 相関なので、 VIF が数百〜数千になる(実質的には推定不能)。 statsmodels はランク落ちを検出して片方の係数を NaN にするか、 警告を出す。 教訓:派生変数を作るときは「片方しか入れない」ように設計する。

📝 ワーク 3: Ridge と Lasso の比較

課題:同じ 4 変数モデルを Ridge と Lasso で推定し、 α を 0.001〜10 まで変化させて係数の軌跡をプロットせよ。

想定解:Ridge は α を上げても全係数が滑らかに 0 に近づく。 Lasso は α=0.1 あたりで β₂, β₃, β₄ が完全にゼロになり、 「実質高齢化率 1 変数モデル」になる。 これが Lasso のスパース性。

📝 ワーク 4: 交差検証で汎化性能を測る

課題:4 変数重回帰の CV R² を、 5-fold と LOO(Leave-One-Out)で比較せよ。

想定解:5-fold CV R² ≈ 0.92(学習時 R² = 0.948 より少し下がる)。 LOO CV R² も同程度。 過学習はほぼ起きていない。 もし大きく乖離していたら変数を絞るべき。

📝 ワーク 5: 残差プロットで仮定診断

課題:4 変数モデルの (1) Residuals vs Fitted, (2) QQ plot, (3) Scale-Location, (4) Cook's distance を 4 枚プロットせよ。 各図から読み取れることを 1 文ずつ書け。

想定解:(1) ランダム雲、 線形性 OK。 (2) ほぼ直線上、 正規性 OK。 (3) 横軸に対して平坦、 等分散性 OK。 (4) Cook's d は全て < 4/47=0.085、 影響度の高い外れ値なし。 → 全仮定支持。

🎮 触って理解する

重回帰の核心は「他の変数を一定にしたときの効果(偏回帰係数)」です。 ここでは 2 つの説明変数 X₁・X₂ から目的変数 Y を作り、 最小二乗法で係数を推定する様子をその場で体感できます。 真の係数はドラッグ、 X₁ と X₂ の相関・ノイズはスライダーで動かしてください。 図と数値がリアルタイムで更新されます。

① 真の係数をドラッグ
横軸=真の β₁、 縦軸=真の β₂。 白い点をドラッグ(タッチ対応)。
大きくすると多重共線性・交絡が発生
大きくすると R² が下がる
④ 偏回帰プロット(他変数の影響を除いた残差同士)
X₁ の偏回帰プロット 傾き = β̂₁
X₂ の偏回帰プロット 傾き = β̂₂

横軸は「他方の変数で説明した残り」、 縦軸は「Y から他方の変数の影響を除いた残り」。 この散布図の傾きがそのまま偏回帰係数になります(added-variable plot)。

⑤ 推定結果
真値最小二乗推定
切片 β₀0.00
β₁ (X₁)
β₂ (X₂)
決定係数 R²
X₁ を単回帰した係数(周辺効果):
X₁ の偏回帰係数 β̂₁(X₂を固定した効果):
多重共線性の指標 VIF = 1/(1−r²):

🧭 直感 — 偏回帰係数は「他を一定にした効果」

β̂₁ は「X₂ を同じ値に固定したまま X₁ を 1 増やすと Y が平均どれだけ動くか」を表します。 上の偏回帰プロットで、 横軸を「X₂ で説明しきれなかった X₁ の残り」に置き換えているのはそのためです。 X₂ の影響をあらかじめ取り除いてから X₁ と Y の関係を見る、 これが「他を一定にする」の数学的な意味です。

⚠️ よくある落とし穴 — 多重共線性と交絡

🚀 発展

説明変数が 3 個以上になると手計算の閉形式は現実的でなくなり、 行列表現 β̂ = (XᵀX)⁻¹Xᵀy で一般化します(このページ内でも既出)。 多重共線性が深刻なときは正則化(リッジ回帰・Lasso)で分散を抑え、 変数のスケールが揃わないときは標準化偏回帰係数で効き目の大きさを比較します。 このミニ実験の「r を上げると推定が暴れる」現象こそ、 それらの手法が必要になる出発点です。

関連ページ: 単回帰相関分析多重共線性交絡標準化最小二乗法(OLS)決定係数自由度調整済み決定係数

🧭 解説深化 — 「係数のズレ幅」は公式で正確に当てられる

本文では「単回帰と重回帰で係数が変わる」ことを繰り返し見ました。 ここでは一歩進んで、 そのズレ幅が偶然ではなく公式で 1 ‰ の桁まで正確に予言できること(脱落変数バイアスの分解式)と、 本文で名前だけ登場した Frisch-Waugh-Lovell(FWL)定理を SSDSE-B-2026 の実数値で「実演」します。 使うのは 2023 年・47 都道府県の死亡率(A4200/A1101×1000、‰)・高齢化率(A1303/A1101×100、%)・年平均気温(B4101、℃)です。 本文 #calc の 4 変数モデルとは違い、 代数を目で追えるようここでは 2 変数モデルに絞るので、 係数の値は #calc(β₁=+0.649)と少し異なります(本節では +0.6067)。

🎨 直感 — 脱落変数バイアス(OVB)の分解式

高齢化率を「入れ忘れた」短い回帰と、 入れた長い回帰を実データで並べます(すべて実測値)。

回帰式(推定結果)
短い回帰(気温のみ)死亡率 = 21.702 − 0.4525×気温(r = −0.442)0.196
長い回帰(高齢化率+気温)死亡率 = −4.857 + 0.6067×高齢化率 − 0.0122×気温0.945
補助回帰(脱落変数を気温で説明)高齢化率 = 定数 − 0.7258×気温(r = −0.445)

OVB の分解式は次の 1 行です。

$$\underbrace{\hat\beta_{\text{短}}}_{\text{気温の見かけの効果}} = \underbrace{\hat\beta_{\text{長}}}_{\text{直接効果}} + \underbrace{\hat\beta_{\text{高齢化}}}_{\text{脱落変数の効果}} \times \underbrace{\hat\delta}_{\text{補助回帰の傾き}}$$

実データで検算:−0.0122 + 0.6067 × (−0.7258) = −0.4525。 短い回帰の傾き −0.4525 と小数第 4 位まで完全一致します(これは近似ではなく OLS の代数的恒等式)。 つまり「気温 1℃ 上昇で死亡率 0.45 ‰ 低下」に見えた効果のうち、 直接分は −0.0122 だけで、 残り −0.4404(約 97 %)は「暖かい県ほど高齢化率が低い(δ = −0.7258)」という経路の間借りでした。 重回帰は魔法ではなく、 見かけの効果をこの 2 つの足し算に分解し直しているだけ——これが「制御する」の正体です。

⚠️ 落とし穴(重要) — バイアスの「向き」は入れる前に読める/入れてはいけない変数もある

分解式 $\hat\beta_{\text{短}} = \hat\beta_{\text{長}} + \hat\beta_{\text{脱落}}\hat\delta$ の積の符号だけ見れば、 脱落変数を測定できていなくてもバイアスの向きが予言できます。

脱落変数の効果 β > 0脱落変数の効果 β < 0
脱落変数と x の相関 δ > 0上振れ(過大評価)下振れ(過小評価)
脱落変数と x の相関 δ < 0下振れ(過小評価)上振れ(過大評価)

上の実例は「β > 0(高齢化は死亡率を上げる)× δ < 0(暖かい県は高齢化率が低い)→ 下振れ」の左下セルで、 実際に −0.0122 が −0.4525 まで下振れしていました。 論文の考察で「未測定の交絡が残る」と書くとき、 この表で残ったバイアスの向きまで議論できると説得力が段違いです。

もう一つの重要な罠 — 「制御しすぎ」(bad control): 落とし穴 15 項目は「入れ忘れ」の話でしたが、 逆に入れてはいけない変数があります。 x → m → y と因果が流れる中間変数 m(媒介変数)を重回帰に入れると、 x の効果のうち m 経由の分が m の係数に吸い取られ、 x の総効果が過小評価されます。 たとえば「高齢化率 → 死亡率」を推定したいときに要介護認定率のような下流変数を制御すると、 高齢化の効果の大部分を自分で消してしまう。 「とりあえず全部入れる」は OVB の逆向きの誤りで、 何を入れ何を入れないかは因果の向き(DAG)で決めるべき問題です。 y の結果である変数(合流点)を入れると存在しない相関が生まれることもあります(本文の Berkson バイアスの回帰版)。

🚀 発展 — FWL 定理を数値で実演する

本文 #breakdown で「β₁ は残差同士の単回帰の傾きと一致する(FWL 定理)」と述べました。 実データで本当に一致するか確かめます。 手順は 3 ステップ:

① 高齢化率を気温で回帰し残差 $e_{x_1}$ を取る(高齢化率から「気温で説明できる分」を抜く)。 ② 死亡率を気温で回帰し残差 $e_y$ を取る。 ③ $e_y$ を $e_{x_1}$ で単回帰する。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4200(死亡数) B4101(年平均気温) 北海道 5,092,000 1,681,000 75,120 11.0 東京都 14,086,000 3,205,000 137,241 17.6 沖縄県 1,468,000 350,000 15,110 23.8 …(全 47 行)
1
2
3
4
5
6
7
8
9
import pandas as pd, numpy as np
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d  = df[df['SSDSE-B-2026']==2023]
y  = (d['A4200']/d['A1101']*1000).values          # 死亡率 ‰
x1 = (d['A1303']/d['A1101']*100).values           # 高齢化率 %
x4 = d['B4101'].astype(float).values              # 年平均気温 ℃
e_x1 = x1 - np.poly1d(np.polyfit(x4, x1, 1))(x4)  # ① 残差
e_y  = y  - np.poly1d(np.polyfit(x4, y , 1))(x4)  # ② 残差
print(np.polyfit(e_x1, e_y, 1))                   # ③ 傾き 0.6067, 切片 ≈ 0

実行結果:残差回帰の傾きは 0.6067 で、 2 変数重回帰の β₁ = 0.6067 と完全一致。 切片は 4.3×10⁻¹⁵(数値誤差の範囲で 0)。 つまり p 変数の重回帰は「p 回の残差取り+単回帰」に分解でき、 偏回帰係数とは文字通り『他の変数の影響を差し引いた後に残る関係』の傾きだと計算レベルで確認できます。

この「partialling out(部分化)」の視点は現代の手法に直結します。 ① パネルデータの固定効果推定は「県ダミー 47 本の重回帰」を FWL で「県平均を引いた残差回帰」に置き換えたもの(within 変換)。 ② 因果推論の DiD も二方向の平均を引く FWL の応用。 ③ 機械学習では、 残差取りの部分を線形回帰でなくランダムフォレスト等に置き換える Double/Debiased Machine Learning(Chernozhukov ら, 2018)が、 高次元の交絡を柔軟に partialling out する標準手法になっています。 なお本節の R² 実測値が示すとおり、 気温は高齢化率の後に足しても R² を 0.945 から実質増やしません(y〜高齢化率のみでも R² = 0.945)——「変数を足す価値」は増分 R²(= 偏決定係数の視点)で測る、 という #python ⑦ のモデル比較にもつながります。