論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説(ジャストインタイム型データサイエンス教育)
パネルデータと因果推論
Panel Data & Causal Inference
観察データから因果効果を取り出す — 相関と因果のあいだ
因果推論パネル経済学政策評価

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

パネル因果推論に関連する手法・概念のチップ集。

固定効果モデル 変量効果モデル Hausman 検定 DiD 並行トレンド Two-way FE Synthetic Control Event Study RDD IV (操作変数) クラスタリング標準誤差 Heterogeneous TWFE Callaway-Sant'Anna propensity score 逆数重み 処置効果(ATE) ATT CATE causal forest 交絡変数

🔖 キーワード索引(チップから該当箇所へジャンプ)

論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:

なぜ因果推論 相関≠因果 交絡 反事実 潜在結果モデル ATE・ATT RCT DID 操作変数 (IV) RDD 傾向スコア マッチング パネルデータ 固定効果 変量効果 Hausman検定 DAG 感度分析

💡 30秒で分かる結論

🍰 まずはやさしく

原因と結果の関係を調べる道具です。

ある行動が本当に効果あったか知るために使います。

勉強法を変えて点数が上がったか調べるようなものです。

ここでは効果を正しく測る方法を学びます。

🤔 1. なぜ因果推論が必要か

機械学習・統計学は「相関や予測」に強い一方、 「もし介入したら何が起きるか」という問いには直接答えられない。

政策評価・医療効果検証・マーケティングの ROI 計算など、 実務の多くは因果推論の問題。

1.1 相関と因果の違い

古典的事例:「アイスクリーム消費量と水難事故数」は強く正相関するが、 因果関係はない(共通原因=気温)。

🕸 2. 交絡(Confounding)

処置 $T$ と結果 $Y$ の両方に影響する第三変数 $C$ があると、 単純な相関では因果を取り出せない。

$$T \leftarrow C \rightarrow Y$$

例:教育年数 $T$ → 賃金 $Y$ の関係。 親の年収 $C$ が両方に影響 → 単純比較では教育の効果が過大評価される。

2.1 DAG(有向非巡回グラフ)

Pearl による因果推論の言語。 矢印 $X \to Y$ で「$X$ が $Y$ の原因」を表す。 「バックドア基準」を満たす調整変数を特定すれば、 観察データから因果効果が同定できる。

🎭 3. 潜在結果モデル(Rubin Causal Model)

個体 $i$ について:

根本問題:同じ個体について両方は観測できない。 観測されるのは $Y_i = T_i Y_i(1) + (1-T_i) Y_i(0)$ のみ。

3.1 ATE と ATT

3.2 反事実

「もし処置を受けていなかったら、 この個体の結果はどうだったか?」を推測することが因果推論の核心。

🎲 4. ランダム化比較試験 (RCT)

処置をランダムに割り当てれば、 処置群と対照群が平均的に類似 → $T \perp \{Y(0), Y(1)\}$ が成り立つ。 ATE は単純な差で不偏推定可能:

$$\widehat{\mathrm{ATE}} = \bar{Y}_{\text{処置}} - \bar{Y}_{\text{対照}}$$

RCT の限界

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np
from scipy import stats
# 想定 RCT 結果:処置群 n=100、 対照群 n=100
# 実験データは手元に無いので、真の効果 0.5 を仕込んだ<架空の>データで手順を示す
_rng = np.random.default_rng(0)
treat = _rng.normal(loc=10.5, scale=2.0, size=100)
ctrl  = _rng.normal(loc=10.0, scale=2.0, size=100)
t, p = stats.ttest_ind(treat, ctrl, equal_var=False)
ate = treat.mean() - ctrl.mean()
se = np.sqrt(treat.var(ddof=1)/len(treat) + ctrl.var(ddof=1)/len(ctrl))
print(f'ATE = {ate:.3f}, 95% CI = [{ate-1.96*se:.3f}, {ate+1.96*se:.3f}]')

📊 5. 差分の差分 (Difference-in-Differences)

処置群と対照群の時間変化の差から因果効果を推定。 共通トレンド仮定が鍵。

$$\widehat{\mathrm{DID}} = (\bar{Y}^{\text{処置}}_{\text{後}} - \bar{Y}^{\text{処置}}_{\text{前}}) - (\bar{Y}^{\text{対照}}_{\text{後}} - \bar{Y}^{\text{対照}}_{\text{前}})$$

5.1 回帰での実装

$$Y_{it} = \beta_0 + \beta_1 \mathrm{Treat}_i + \beta_2 \mathrm{Post}_t + \beta_3 (\mathrm{Treat}_i \times \mathrm{Post}_t) + \varepsilon_{it}$$

$\beta_3$ が DID 推定量。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) L3221(消費支出(二人以上の世帯)) A1303(65歳以上人口) A1101(総人口) 北海道 2,023 296,888 1,681,000 5,092,000 東京都 2,023 341,320 3,205,000 14,086,000 沖縄県 2,023 251,222 350,000 1,468,000 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# ── この抜粋で使うパネルデータを用意します ──
# 「高齢化率が中央値超の県を処置群、2016 年以降を Post」とみなした例。
import pandas as pd
import statsmodels.formula.api as smf

panel = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
panel = panel.rename(columns={'年度': 'Year'})
panel['Y'] = panel['消費支出(二人以上の世帯)'].astype(float)
_aging = panel['65歳以上人口'] / panel['総人口'] * 100
panel['Treat'] = (_aging >= _aging.median()).astype(int)
panel['Post'] = (panel['Year'] >= 2016).astype(int)

# パネルデータ:複数都道府県の時点 × 処置有無
model = smf.ols('Y ~ Treat + Post + Treat:Post + C(都道府県)', data=panel).fit()
print(model.summary())
# Treat:Post の係数が DID 推定量

5.2 共通トレンド仮定の検証

処置前期間で処置群・対照群のトレンドが平行か → イベントスタディ・プラセボ検定。

🔧 6. 操作変数法 (Instrumental Variable)

処置 $T$ と結果 $Y$ の両方に影響する未観測交絡があるとき、 次の条件を満たす変数 $Z$(操作変数)を使って因果を識別:

6.1 2段階最小二乗法 (2SLS)

  1. 第1段階:$T$ を $Z$ で回帰 → 予測値 $\hat{T}$
  2. 第2段階:$Y$ を $\hat{T}$ で回帰
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 骨格ではなく実データで 2SLS を動かす。
# Y=消費支出、内生変数 T=高齢化率、操作変数 Z=年少人口比率、統制 X1/X2
# (Z はあくまで手順を示すための例。除外制約が満たされる保証は無い)
from linearmodels.iv import IV2SLS

df = panel.copy()                                      # 上のブロックで作った panel を使う
df['T']  = df['65歳以上人口'] / df['総人口'] * 100      # 内生変数:高齢化率(%)
df['Z']  = df['15歳未満人口'] / df['総人口'] * 100      # 操作変数:年少人口比率(%)
df['X1'] = df['総人口'] / 1e4                           # 統制:総人口(万人)
df['X2'] = df['婚姻件数'] / df['総人口'] * 1000         # 統制:婚姻率(‰)

mod = IV2SLS.from_formula('Y ~ 1 + [T ~ Z] + X1 + X2', data=df).fit()
print(mod.params.round(2))
print('第 1 段階 F 値:', round(float(mod.first_stage.diagnostics['f.stat'].iloc[0]), 2))

6.2 古典例

📏 7. 回帰不連続デザイン (RDD)

連続変数 $X$ の閾値 $c$ を境に処置が変わる場合、 閾値前後で他の条件はほぼ同じ → 閾値での結果のジャンプが因果効果。

$$\tau_{\mathrm{RDD}} = \lim_{x\downarrow c}\mathbb{E}[Y|X=x] - \lim_{x\uparrow c}\mathbb{E}[Y|X=x]$$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd

# ── RDD の題材を用意する。高齢化率 30% を境目 (cutoff) とみなした例 ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = pd.DataFrame({
    'X': _d['A1303'] / _d['A1101'] * 100,        # running variable: 高齢化率(%)
    'Y': _d['L3221'].astype(float),               # outcome: 消費支出
})
cutoff = 30.0

import numpy as np
from rdrobust import rdrobust
result = rdrobust(y=df['Y'], x=df['X'], c=cutoff)
print(result)

⚖️ 8. 傾向スコア (Propensity Score)

処置確率 $e(x) = P(T=1|X=x)$ を共変量から推定(ロジスティック等)。 「条件付き独立性(CIA)」が成り立てば、 傾向スコアでバランスを取れば因果効果が同定可。

8.1 マッチング

処置群の各個体に「傾向スコアが近い対照群個体」をマッチ。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) B4101(年平均気温) 北海道 5,092,000 1,681,000 11.0 東京都 14,086,000 3,205,000 17.6 沖縄県 1,468,000 350,000 23.8 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ── この抜粋で使うデータを用意します ──
import pandas as pd
from sklearn.linear_model import LogisticRegression

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.reset_index(drop=True)
df = df.dropna(subset=['A1101', 'B4101', 'A1303'])   # NaN があると最近傍探索が落ちる
X_covariates = df[['A1101', 'B4101']].astype(float).values
_aging = df['A1303'] / df['A1101'] * 100
T = (_aging >= _aging.median()).astype(int).values   # 処置の有無
df['T'] = T

from sklearn.preprocessing import StandardScaler
# 傾向スコア推定
ps_model = LogisticRegression(max_iter=1000).fit(X_covariates, T)
df['ps'] = ps_model.predict_proba(X_covariates)[:, 1]
# 最近傍マッチング
from sklearn.neighbors import NearestNeighbors
# df.T は「転置」の意味になってしまう。列を指すときは df['T'] と書く。
nn = NearestNeighbors(n_neighbors=1).fit(df[df['T'] == 0][['ps']])
dist, idx = nn.kneighbors(df[df['T'] == 1][['ps']])
# マッチ後の差で ATT 推定

8.2 IPW(逆確率重み付け)

$$\widehat{\mathrm{ATE}}_{\mathrm{IPW}} = \frac{1}{n}\sum_{i} \left[\frac{T_i Y_i}{\hat{e}(X_i)} - \frac{(1-T_i)Y_i}{1-\hat{e}(X_i)}\right]$$

処置確率の逆数で重み付けして単純差を取る。

8.3 二重頑健推定 (DR)

傾向スコアと結果モデルの両方を使い、 どちらか一方が正しければ一致推定量。

📋 9. パネルデータ

同じ個体(人・企業・都道府県)を複数時点で観察したデータ。 個体固有の異質性を制御できる。

9.1 固定効果モデル (FE)

$$Y_{it} = \alpha_i + \boldsymbol{\beta}^\top \mathbf{x}_{it} + \varepsilon_{it}$$

$\alpha_i$ は個体ごとの切片(時間不変の異質性)。 個体内変動だけで $\beta$ を識別。 時間不変の交絡が自動的に消える。

🎯 目的:linearmodels の PanelOLS で固定効果モデルを推定し、 SSDSE-B-2026 を年度 × 都道府県のパネルに整形して時間不変の県効果を除去する。
📥 入力data/raw/SSDSE-B-2026.csv。 MultiIndex (都道府県, 年度) で整形。 説明:高齢化率(A1303 / A1101 × 100)、 目的:消費支出(L3221)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from linearmodels.panel import PanelOLS

# 上のブロックで作った panel に説明変数を足し、都道府県 × 年の 2 重添字にする
panel['X1'] = panel['総人口'] / 1e4                          # 総人口(万人)
panel['X2'] = panel['65歳以上人口'] / panel['総人口'] * 100  # 高齢化率(%)
df_p = panel.set_index(['都道府県', 'Year'])   # 年の列名は 'Year'('年' ではない)

fe = PanelOLS.from_formula('Y ~ X1 + X2 + EntityEffects', data=df_p).fit()
print(fe.params.round(4))
print('within R2 =', round(fe.rsquared_within, 6))
📤 出力:Within R²≈0.00(実測:高齢化率→消費支出)。 県固定効果を除去すると高齢化率の係数はほぼゼロになる。 標準誤差はクラスター(県)でロバスト化。
💬 解釈:パネル因果の鉄則:(1)固定効果で県別レベル差を除去、(2)クラスター SE で時系列相関補正、(3)並行トレンド仮定の事前検証。

9.2 変量効果モデル (RE)

$\alpha_i$ をランダム変数とみなす。 説明変数と $\alpha_i$ が独立なら FE より効率的だが、 仮定が崩れると不偏でなくなる。

9.3 Hausman 検定

FE と RE の係数が体系的に違うか検定。 違いが有意なら FE を採用すべき。

🎯 目的:linearmodels の PanelOLS で固定効果モデルを推定し、 SSDSE-B-2026 を年度 × 都道府県のパネルに整形して時間不変の県効果を除去する。
📥 入力data/raw/SSDSE-B-2026.csv。 MultiIndex (都道府県, 年度) で整形。 説明:高齢化率(A1303 / A1101 × 100)、 目的:消費支出(L3221)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from linearmodels.panel import PanelOLS, RandomEffects

panel['X'] = panel['65歳以上人口'] / panel['総人口'] * 100   # 高齢化率(%)
df_p = panel.set_index(['都道府県', 'Year'])
fe  = PanelOLS.from_formula('Y ~ X + EntityEffects', data=df_p).fit()
re_ = RandomEffects.from_formula('Y ~ 1 + X', data=df_p).fit()   # re は標準モジュール名なので re_ にする

# Hausman 統計量を手で計算:(b_FE - b_RE)^2 / (Var_FE - Var_RE)
d = float(fe.params['X'] - re_.params['X'])
v = float(fe.cov.loc['X', 'X'] - re_.cov.loc['X', 'X'])
print('FE  beta =', round(float(fe.params['X']), 4))
print('RE  beta =', round(float(re_.params['X']), 4))
print('Hausman  =', round(d ** 2 / v, 4))
📤 出力:Within R²≈0.00(実測:高齢化率→消費支出)。 県固定効果を除去すると高齢化率の係数はほぼゼロになる。 標準誤差はクラスター(県)でロバスト化。
💬 解釈:パネル因果の鉄則:(1)固定効果で県別レベル差を除去、(2)クラスター SE で時系列相関補正、(3)並行トレンド仮定の事前検証。

9.4 双方向固定効果(TWFE)

個体効果 $\alpha_i$ + 時間効果 $\delta_t$。 DID と同等。

🔍 10. 感度分析

未観測交絡があった場合に結論がどれだけ変わるか評価。

📊 11. 因果推論手法の使い分け

手法 必要な条件 推定する効果
RCTランダム割当ATE
DID共通トレンド + 処置タイミングATT
IV関連性 + 外生性LATE
RDD明確な閾値境界での効果
傾向スコア条件付き独立性 (CIA)ATE/ATT
固定効果パネルデータ + 時間不変交絡時間内変動効果
合成統制法十分な対照ユニット単一処置ユニットの効果

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

🍰 まずはやさしく

データの分析手法をまとめた地図のようなページです。

データから本当の原因を見つけるために使います。

スマホの利用時間と成績の関係を正しく分析する例です。

因果関係を取り出すための仕組みを体系的に読みます。

本ページでは、 パネルデータ分析因果推論を統合的に解説します。 固定効果・変量効果RCTDIDIVRDD傾向スコア交絡を一気通貫で扱います。

「相関は因果ではない」は有名な格言。 では観察データから因果を取り出すにはどうすればよいか? 計量経済学・統計疫学・社会科学で発展した方法論を体系的に学びます。

🎨 直感で掴む — パネル因果分析

🍰 まずはやさしく

同じ人をずっと追いかける観察方法です。

人それぞれのクセを無視して効果を測るために使います。

部活で同じ人が練習法を変えた時の変化を見る例です。

まずはイメージで全体の流れを掴んでください。

パネル因果分析は「同じ個体(人・地域・企業)を複数時点で観察し、 個体の固定効果を制御した上で介入効果を推定する」枠組み。 観察できない個体差をパネル構造で吸収できるのが強み。 SSDSE-B-2026 は 2012-2023 の 12 年×47 県のパネルなので、 県固定効果+年固定効果+政策ダミーで効果検証できる。

💡 学習のコツ:直感で全体像を掴んだら、 次の「📐 定義・数式」で正確な意味を押さえ、 最後に「🧮 実値で計算してみる」で SSDSE-B-2026 の都道府県データを使った計算をなぞるのが効率的です。 比喩は厳密ではないので、 必ず数式と並べて確認してください。

パネル因果分析 は「因果推論」カテゴリの中核概念。 初めて触れる読者は、 まずこの「🎨 直感」セクションだけ通読し、 必要になった時点で「📐 数式」「🐍 Python」「⚠️ 落とし穴」へ戻る読み方が定着しやすいです。

📐 定義・数式 — パネル因果分析

🍰 まずはやさしく

分析の内容を記号で書いたルールブックです。

誰が読んでも同じ意味になるようにするために使います。

テストの点数を計算式で表すようなものです。

数式の記号が何を意味するかを詳しく読みます。

やさしい説明で掴んだ感覚を、ここで パネル因果分析 の定義式に対応づけます。下の式は左辺 $y_{it}$ が何で決まるかを右辺で書き下したもので、β(回帰係数)、α(有意水準など) が現れます。それぞれの記号が何の量を指すのかは、次の「🔬 数式を言葉で読み解く」で 1 つずつ確かめてください。

【パネル因果分析 の中心定義式】
$$ y_{it} = \alpha_i + \gamma_t + \beta D_{it} + \varepsilon_{it} $$
この式が「パネル因果分析」の骨格。 派生形・拡張形はここから生まれる。
📌 読み方のコツ:数式を見たら「左辺は何を定義しているか」「右辺の各項は何の合計・積・比か」を声に出して読み下してみる。 これだけで理解が大きく進みます。

🔬 数式を言葉で読み解く — 数式を「言葉」に翻訳

上の数式を眺めるだけでは身につかないので、 各記号がどんな役割を担っているかを言葉で押さえます。 「数式を音読する習慣」がつくと、 論文や教科書を読むスピードが体感で 2 倍ほど上がります。

左辺(結果側)
パネル因果分析 で定義したい量。 解釈の対象。 単位・スケールを必ず確認する。
右辺(構成要素)
観測できる入力変数(SSDSE-B-2026 でいえば A1101・L3221 など)と推定対象パラメータ(β, σ 等)の組合せ。
添字 i, j, t
i=サンプル(県)、 j=変数、 t=時点。 SSDSE-B-2026 は i ∈ {1..47} 県、 t ∈ {2012..2023}。
和記号 Σ
「足し合わせ」を表す。 添字 i が 1 から n まで動く範囲を明示するのが習慣。
期待値 E[·]、 分散 Var[·]
「ランダム変数の平均」と「ばらつき」。 SSDSE-B-2026 のような集計値でも、 標本誤差・年次変動の文脈で使える。
📚 補足:同じ記号でも分野・教科書によって意味が違うことがあります(例: $\hat{y}$ は予測値だが、 統計の文脈では推定量を意味することも)。 不明確なときは、 必ずその文書の記号定義表を確認しましょう。

🧮 SSDSE-B-2026 で実値計算 — パネル因果推論の実例

SSDSE-B-2026 は 47都道府県 × 複数年のパネル構造を持つので、 固定効果モデル・DiD の練習に最適。

例:固定効果モデル(two-way FE)

🎯 目的:linearmodels の PanelOLS で固定効果モデルを推定し、 SSDSE-B-2026 を年度 × 都道府県のパネルに整形して時間不変の県効果を除去する。
📥 入力data/raw/SSDSE-B-2026.csv。 MultiIndex (都道府県, 年度) で整形。 説明:高齢化率(A1303 / A1101 × 100)、 目的:消費支出(L3221)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import pandas as pd
from linearmodels import PanelOLS
import statsmodels.api as sm

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={'SSDSE-B-2026': '年度', 'L3221': '消費支出'})
df['高齢化率'] = df['A1303'] / df['A1101'] * 100  # 65歳以上人口 / 総人口
df = df.set_index(['Prefecture', '年度'])  # パネル構造:県 × 年
y = df['消費支出']
X = sm.add_constant(df[['高齢化率']])

model = PanelOLS(y, X, entity_effects=True, time_effects=True).fit(
    cov_type='clustered', cluster_entity=True)
print(model)

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

数式だけでは「実感」が湧きにくいので、 実データ data/raw/SSDSE-B-2026.csv(47 都道府県 × 12 年)で 1 度手計算してみると理解が定着します。

SSDSE-B-2026 を使い、 「2020 年コロナ以降で L3221(消費支出)が下がったか」を固定効果回帰で推定する。 県固定効果 47 個+ Post 2020 ダミーで OLS(Post は年固定効果の一次結合なので、 年ダミーと併用すると完全共線になる点に注意)。 47×12=564 観測。 実測では Post 2020 係数は約 -1,187 円(県クラスタロバスト t = -0.68、 非有意)で、 県差を取り除いた「コロナ前後の平均差」に統計的に明確な低下は確認されない。

都道府県A1101 総人口A1303 65 歳以上L3221 消費支出
東京都14,086,0003,205,000341,320
神奈川県9,229,0002,390,000306,565
大阪府8,763,0002,424,000271,246
愛知県7,477,0001,923,000300,221
埼玉県7,331,0002,012,000344,092
千葉県6,257,0001,756,000306,943

上記は SSDSE-B-2026 (2023) からの抜粋。 手計算で確認した値が、 後述の Python 実装で得る値と一致することを確認すると、 「数式とコードの対応関係」がクリアに見えるようになります。

🧮 数式に値を入れて手で計算する: パネルデータの因果推定

合成データで個体固定効果 + DID で因果効果を推定する。

Step 1: 2 個体 × 2 期

個体期 0 (前)期 1 (後)
処置 i=1514
対照 i=237

Step 2: DID

処置差 = 14 - 5 = 9 対照差 = 7 - 3 = 4 DID = 9 - 4 = +5 (因果効果)

🐍 Python で再現

1
2
3
4
treat = [5, 14]
ctrl = [3, 7]
did = (treat[1]-treat[0]) - (ctrl[1]-ctrl[0])
print(f"DID: {did}")

📤 実行結果

DID: 5

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

🐍 15. ライブラリ早見表

用途 関数・パッケージ
パネル回帰linearmodels.panel.PanelOLS, RandomEffects
2SLSlinearmodels.iv.IV2SLS
DIDstatsmodels.formula.api.ols, differences (PyPI)
RDDrdrobust (PyPI)
傾向スコアcausalinference, doWhy, EconML
マッチングcausalml, dowhy
合成統制法SyntheticControlMethods (PyPI)
DAGnetworkx, daggity (R), pgmpy
CATE推定EconML, causalml
DoWhy(統合)dowhy (Microsoft)

📜 16. 因果推論の歴史

💼 17. 実務応用

✅ 18. 因果推論チェックリスト

🚀 19. 発展トピック

19.1 合成統制法(Synthetic Control Method)

2003 Abadie ら。 単一処置ユニット(例:1 つの州・国)に対し、 他のユニットの加重平均で「人工的な対照」を作る。

例:カリフォルニア州のタバコ税引上の効果評価。 他の州の加重平均で「タバコ税のない仮想カリフォルニア」を作り比較。

19.2 異質処置効果(CATE)

個体・部分集団ごとの処置効果。 機械学習で推定(Causal Forest、 X-Learner、 R-Learner、 DR-Learner)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
import numpy as np

# ── CausalForestDML に渡す変数を用意する ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_aging = _d['A1303'] / _d['A1101'] * 100
y = _d['L3221'].astype(float).values                       # 結果: 消費支出
t = (_d['SSDSE-B-2026'] >= 2016).astype(int).values        # 処置: 2016 年以降
X_features = np.column_stack([_aging, np.log(_d['A1101'].astype(float))])
W_controls = _d[['A4101', 'A9101']].astype(float).values   # 統制変数

from econml.dml import CausalForestDML
cf = CausalForestDML(model_y='auto', model_t='auto', discrete_treatment=True)
cf.fit(Y=y, T=t, X=X_features, W=W_controls)
cate = cf.effect(X_features)  # 各個体の処置効果

19.3 二重機械学習(DML)

Chernozhukov ら 2018。 結果と処置を高次元共変量から ML で予測し、 残差で因果効果を推定。 高次元共変量にも対応。

19.4 Doubly Robust ML

傾向スコアと結果モデルの両方を ML で推定し、 どちらか一方が正しければ一致。

🤖 20. 因果推論と機械学習の融合

アップリフトモデリング

「処置すれば反応する人」を予測(CATE が大きい人)。 マーケティング配信の効率化。

❓ 21. よくある質問

Q. 単純な OLS で交絡変数を入れれば因果が出る?

A. すべての交絡が観測されていれば原理的には可能(CIA)。 ただし「すべてを観測」は実務でほぼ不可能。 識別戦略を明示し、 未観測交絡への感度分析が必須。

Q. DID とパネル固定効果はどう違う?

A. 数学的にはほぼ同じ。 DID は「2 群 × 2 時点」の単純設定、 双方向固定効果は同じロジックを多群・多時点に一般化したもの。

Q. RCT があれば因果推論手法は要らない?

A. RCT は黄金律ですが、 倫理・コスト・遵守率・外的妥当性の問題があり、 観察データの分析が必要な場面は多い。 また RCT 内でも CATE 推定は ML 因果推論が必要。

Q. 単年度の横断データしかない場合でも因果推論できる?

A. 横断データだけでは因果推論は本来困難。 ドメイン知識で交絡を網羅し、 傾向スコアで「条件付き因果関係」と限定的に解釈するのが現実的。

1. linearmodels — PanelOLS(FE/RE)

🎯 目的:linearmodels の PanelOLS で固定効果モデルを推定し、 SSDSE-B-2026 を年度 × 都道府県のパネルに整形して時間不変の県効果を除去する。
📥 入力data/raw/SSDSE-B-2026.csv。 MultiIndex (都道府県, 年度) で整形。 説明:高齢化率(A1303 / A1101 × 100)、 目的:消費支出(L3221)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from linearmodels.panel import PanelOLS, RandomEffects, compare
import pandas as pd
import statsmodels.api as sm

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df.rename(columns={'SSDSE-B-2026': '年度', 'L3221': '消費支出'})
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
df = df.set_index(['Prefecture', '年度'])
y, X = df['消費支出'], sm.add_constant(df[['高齢化率']])
fe = PanelOLS(y, X, entity_effects=True).fit(cov_type='clustered', cluster_entity=True)
re = RandomEffects(y, X).fit(); print(compare({'FE': fe, 'RE': re}))

🐍 Python 実装 — パネル因果分析

公的統計(SSDSE-B-2026)を題材に、 最小限の Python コードで パネル因果分析 を動作させます。 まずはこのまま実行してみてください。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 296,888 東京都 14,086,000 3,205,000 341,320 沖縄県 1,468,000 350,000 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# パネル因果分析 を SSDSE-B-2026 で実行する最小コード
import pandas as pd
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]  # 2023 年のみ抽出
print(df.shape)  # (47, 112)
print(df[['Prefecture','A1101','A1303','L3221']].head())

import statsmodels.api as sm
# panel = 全年度 (47 県 × 12 年 = 564 行) を使う
panel = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
panel['post'] = (panel['SSDSE-B-2026'] >= 2020).astype(int)
# 県ダミー + post(post は年ダミーの一次結合なので年ダミーとは併用しない)
X = pd.get_dummies(panel[['Prefecture','post']],
                   columns=['Prefecture'], drop_first=True)
X = X.astype(float)
X = sm.add_constant(X)
y = panel['L3221'].astype(float)
res = sm.OLS(y, X).fit(cov_type='cluster', cov_kwds={'groups': panel['Prefecture']})
print('post係数:', res.params['post'])   # 実測: 約 -1187 円
print('post t:', res.tvalues['post'])    # 実測: -0.68 (非有意)

▶ 実行 を押せばこのページの中でそのまま動きます(ライブラリもデータも同梱済みで、 準備は要りません)。 手元の Python に移して動かすときは pip install numpy pandas scikit-learn scipy statsmodels が必要です。 読んでいるデータは data/raw/SSDSE-B-2026.csv。 日本語を含むので encoding='cp932' の指定を落とさないでください。

⚠️ 12. よくある落とし穴

落とし穴 対処
「相関=因果」と書く識別戦略を明示しない限り「関連が見られた」に留める。
媒介変数を調整処置の下流変数を入れると効果が消える。 DAG で確認。
合流点を調整処置と結果の両方に影響される変数を調整するとバイアスが新たに生じる。
DID で共通トレンド未確認処置前期間のプレトレンドをプロット。
弱いIV第1段階 F > 10 を目安。 弱IVは強いバイアス。
傾向スコアの overlap 不足分布を可視化、 共通サポート領域に絞る。
標準誤差をクラスタリングしないパネルでは個体クラスタロバスト SE を使う。

🏋️ 13. 練習問題

SSDSE-B-2026 は 47 都道府県 × 12 年(2012-2023)= 564 行のバランス・パネルなので、 固定効果・DID の練習にそのまま使えます。

Q1. 都道府県パネルで固定効果回帰を行い、 OLS との係数の違いを観察しなさい。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) L3221(消費支出(二人以上の世帯)) A1303(65歳以上人口) A1101(総人口) Prefecture(都道府県) 北海道 2,023 296,888 1,681,000 5,092,000 北海道 東京都 2,023 341,320 3,205,000 14,086,000 東京都 沖縄県 2,023 251,222 350,000 1,468,000 沖縄県 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pandas as pd
from linearmodels.panel import PanelOLS
import statsmodels.formula.api as smf

# 直前のブロックで panel を英字コード列で読み直しているので、日本語列名で読み直す
panel = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
panel = panel.rename(columns={'年度': 'Year'})
panel['Y'] = panel['消費支出(二人以上の世帯)'].astype(float)
panel['X'] = panel['65歳以上人口'] / panel['総人口'] * 100

df_p = panel.set_index(['都道府県', 'Year'])
ols = smf.ols('Y ~ X', data=panel).fit()
fe  = PanelOLS.from_formula('Y ~ X + EntityEffects', data=df_p).fit()
print('OLS beta:', round(float(ols.params['X']), 4))
print('FE  beta:', round(float(fe.params['X']), 4))
Q2. ある政策が一部の都道府県のみで導入されたケースを想定し、 DID で効果を推定しなさい。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 SSDSE-B-2026(年度) L3221(消費支出(二人以上の世帯)) A1303(65歳以上人口) A1101(総人口) Prefecture(都道府県) 北海道 2,023 296,888 1,681,000 5,092,000 北海道 東京都 2,023 341,320 3,205,000 14,086,000 東京都 沖縄県 2,023 251,222 350,000 1,468,000 沖縄県 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# ── この抜粋で使うパネルデータを用意します ──
import pandas as pd

panel = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)
panel = panel.rename(columns={'年度': '年'})
panel['Y'] = panel['消費支出(二人以上の世帯)'].astype(float)
_aging = panel['65歳以上人口'] / panel['総人口'] * 100
panel['Treat'] = (_aging >= _aging.median()).astype(int)
panel['Post'] = (panel['年'] >= 2016).astype(int)
# 47 県 × 12 年すべてを使うと固定効果の数が多すぎるので、6 県 × 12 年に絞る
_use = ['北海道', '東京都', '愛知県', '大阪府', '高知県', '沖縄県']
panel = panel[panel['都道府県'].isin(_use)]

import statsmodels.formula.api as smf
m = smf.ols('Y ~ Treat*Post + C(都道府県) + C(年)', data=panel,
            cluster_kwds={'groups': panel['都道府県']}).fit()
print(m.summary())
Q3. SSDSE-B 単年データで「都市規模高低」を処置とみなし、 傾向スコアで ATT を推定しなさい。
📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4103(合計特殊出生率) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 1,681,000 1.06 296,888 東京都 14,086,000 3,205,000 0.99 341,320 沖縄県 1,468,000 350,000 1.6 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import pandas as pd
from sklearn.linear_model import LogisticRegression
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023].copy()  # 単年 (2023) の横断で練習
df['T'] = (df['A1101'] >= df['A1101'].median()).astype(int)  # 都市規模高低
df['高齢化率'] = df['A1303'] / df['A1101'] * 100
X = df[['高齢化率', 'A4103']]  # 共変量: 高齢化率・合計特殊出生率
ps = LogisticRegression(max_iter=1000).fit(X, df['T']).predict_proba(X)[:, 1]
# IPW で消費支出 (L3221) への ATE 推定(要:注意深く解釈)
ate = (df['T'] * df['L3221'] / ps).mean() - ((1-df['T']) * df['L3221'] / (1-ps)).mean()
print(f'IPW-ATE = {ate:.0f}')

📝 14. 報告フォーマット

❌ NG例

「都市規模が大きい県ほど消費支出が多い、 という因果関係を確認した。」

✅ OK例

「DID(処置:政策導入 2022 年、 処置群 5 県、 対照群 42 県、 期間 2020-2024)で推定。 処置効果 = 23.4 万円 (95% CI [12.1, 34.7]、 都道府県クラスタロバスト SE)。 プレトレンドはイベントスタディで概ね平行(処置前のリード係数は有意でない)。 ただし共通トレンド仮定の検証は限定的で、 結果は条件付き因果関係として解釈すべき。」

⚠️ 識別仮定が成立しないと因果効果は推定できない

DID・自然実験を使うときの最も重要な仮定は「平行トレンド」です。

⚠️ パネル因果推論の落とし穴(補強・各 100 文字以上)

① 並行トレンド仮定を確認せず DiD を回す
DiD の核心は「処置がなければ処置群と対照群のトレンドは平行だった」という仮定。 これが破れていれば、 推定された処置効果は単なるトレンドの差。 介入前のプレトレンド検査(event study, placebo test)を必ず実施し、 平行が見えなければ Synthetic Control 等の代替を検討する。
② Two-way FE で「処置タイミングが異なる場合」のバイアス
処置時点がユニット間で異なる staggered adoption 設定で、 単純な two-way FE は負のウェイトを生むことが Goodman-Bacon 等で示された。 既処置の県を後発の対照群として使う「悪い比較」が含まれる。 Callaway-Sant'Anna, de Chaisemartin-D'Haultfœuille, Sun-Abraham の推定量を使う。
③ クラスタリング標準誤差を忘れる
パネルデータでは同一ユニット内の観察値は相関するため、 OLS の標準誤差は過小評価される。 これにより p 値が不当に小さく出て、 偽発見が増える。 必ず entity(県や個人)レベルでクラスタリングした標準誤差を使う。 statsmodels なら cov_type='cluster', cluster_groups で対応。
④ 変量効果(RE)が成り立つ条件を吟味しない
変量効果モデルは「個別効果 u_i が説明変数と無相関」という強い仮定が必要。 これが破れていれば(観察データではほぼ常に破れる)RE は不一致推定量。 Hausman 検定で RE と FE を比較し、 有意に違えば FE を選ぶ。 実務では基本 FE 一択と考える人が多い。
⑤ 操作変数 (IV) の弱相関を見落とす
IV を使うと内生性を解決できるが、 IV と内生変数の相関が弱い(First-stage F < 10)と、 推定量の分散が爆発し点推定がほぼ無意味になる。 First-stage F 統計量を必ず報告し、 弱 IV であれば LIML や anderson-rubin 検定を用いる。 「IV を使えば因果」と短絡しない。
⑥ Synthetic Control の比較群を信用しすぎる
SCM は処置前期の合致度で重みを決めるが、 介入後の外挿で過信は禁物。 重みの分布、 ドナー候補のリスト、 プラセボ検定 (in-time / in-space) を報告するのが標準。 Abadie 自身が「重みが疎な解は不安定」と警告。 必ずロバストネスを複数の方法で確認する。
⑦ パネルが「不均衡(unbalanced)」であることを無視
ユニットによって観察期間が違うパネルは欠落のメカニズムが結果と相関し、 選択バイアスを生む。 例:成績不振者だけ追跡を中止するなど。 attrition の理由を確認し、 IPW (inverse probability weighting) や Heckman 補正の必要を検討する。 「データが揃っている県」だけを使うとバイアスが残る。

⚠️ よくある落とし穴 — パネル因果分析

パネル因果分析 (固定効果・DiD・IV) で実務的に最も多い失敗は (1) Pooled OLS と FE の混同で時間不変な交絡を放置、 (2) 平行トレンド仮定の未検証、 (3) クラスター標準誤差の付け忘れ の 3 つ。 SSDSE-B-2026 のように 47 都道府県 × 複数年の構造では、 同一県内の年次誤差が強く相関するため、 t 値・p 値が見かけ上小さくなり「有意」 と誤判断するリスクがある。

❌ Pooled OLS と Fixed Effects を混同
個体差を取り除いていない OLS は内生性で偏る。 必ず固定効果 or 1 階差分。
❌ 並行トレンド仮定
DID では介入前のトレンドが処置群・対照群で平行と仮定する。 グラフで必ず確認。
❌ クラスター標準誤差を忘れる
県内の誤差は時間方向に相関する。 cov_type='cluster' で県別クラスタリング必須。
🛡 防御策まとめ:「適用条件を確認する」「結果と前提をセットで記述する」「不確実性を必ず併記する」の 3 点を習慣化すれば、 上記の罠の大半は回避できます。

🗺 概念マップ

パネル因果推論を中心に、 入力 (パネルデータ・個体 ID×時点)、 主要手法 (差分の差分 DiD・固定効果モデル・合成統制法・操作変数法)、 識別仮定 (並行トレンド・SUTVA・無作為割当)、 検定 (Hausman 検定・プラセボ検定)、 周辺概念 (交絡・因果・反事実) を配置した SVG マップ。

panel causal 並行トレンド仮定 DID・固定効果 合成統制法・DML SSDSE 政策評価 操作変数法 (IV) Hausman 検定

本セクションでは パネル因果分析の 固定効果モデル・差分の差分 (DID)・操作変数 (IV) との組合せを補足する。 SSDSE-B のような複数年・複数都道府県データは、 時間固定効果と県固定効果を同時に入れることで「観測されない交絡」のかなりを吸収でき、 政策効果評価の説得力が増す。

🔗 隣接手法への橋渡し

「パネル因果推論」は単独で完結せず、 前後の手法と組み合わさって価値が発揮される。 入力データの準備 (上流)・同目的の代替手法との比較 (並列)・結果の活用 (下流) という 3 軸で隣接領域を整理する。

この上流・並列・下流の対応を地図化することで、 「パネル因果推論」を中核に据えた分析パイプライン (データ準備 → 手法選択 → 結果の検証と展開) の全体像が見えてくる。

🌳 手法選択フロー

「パネルデータの因果推論」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。

  1. 個体を追跡できているか
    同じ都道府県を複数年にわたって観測しているなら固定効果が使える。 SSDSE-B-2026 は 47 県 × 12 年度なので、 県ごとの時間不変な差を落とせる。 毎年別の対象なら、 それは繰り返し横断データで別の扱いになる。
  2. 落としたいのは何の差か
    県ごとの気質・地理のように時間で変わらない要因は固定効果で落ちる。 全国一律の景気変動は年次ダミーで落ちる。 両方入れる(二元固定効果)のが基本形。
  3. 固定効果か、 変量効果か
    個体効果が説明変数と相関していそうなら固定効果。 相関が無いと仮定できるなら変量効果のほうが効率的。 ハウスマン検定はこの仮定を検討する材料になる。
  4. 標準誤差をどう計算するか
    同じ県の観測は年をまたいで相関する。 通常の標準誤差では過小評価になるので、 県単位でクラスタ頑健にする。

固定効果で落とせるのは「時間で変わらない差」だけ。 年ごとに変わる未観測要因が残っていれば、 依然として因果とは言えない。

3. econml — Causal Forest(heterogeneous treatment effect)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from econml.dml import CausalForestDML
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
# 47 県 × 12 年 = 564 行のパネルをプールして使用
Y = df['L3221'].values  # 消費支出
T = (df['A1101'] >= df['A1101'].median()).astype(int).values  # 都市規模
X = df[['A1303', 'A4103', 'I5102', 'G7101', 'H1800']].values

cf = CausalForestDML(
    model_y=RandomForestRegressor(n_estimators=100),
    model_t=RandomForestClassifier(n_estimators=100),
    discrete_treatment=True
)
cf.fit(Y, T, X=X)
print('CATE 平均:', cf.effect(X).mean())

4. scipy — Hausman 検定の手書き

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import numpy as np
from scipy.stats import chi2
from linearmodels.panel import PanelOLS, RandomEffects  # fe / re はこの推定結果

# fe, re は linearmodels の推定結果
b_fe = fe.params.values
b_re = re.params.values
v_fe = fe.cov.values
v_re = re.cov.values

diff = b_fe - b_re
var_diff = v_fe - v_re
stat = diff @ np.linalg.pinv(var_diff) @ diff
pval = 1 - chi2.cdf(stat, df=len(diff))
print(f'Hausman 統計量={stat:.3f}, p={pval:.4f}')

5. dowhy — 因果推論パイプライン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from dowhy import CausalModel
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df['T'] = (df['A1101'] >= df['A1101'].median()).astype(int)  # 都市規模高低
df['Y'] = df['L3221']  # 消費支出

# 共通原因: 高齢者数・合計特殊出生率・一般診療所数
model = CausalModel(data=df, treatment='T', outcome='Y',
                    common_causes=['A1303', 'A4103', 'I5102'])
identified = model.identify_effect()
estimate = model.estimate_effect(identified, method_name='backdoor.linear_regression')
print('ATE:', estimate.value)

🎮 触って理解する

パネルデータの核心は、同じ個体(ここでは都道府県などの単位)を複数時点にわたって追跡することで、観測できない時間不変の交絡(個体固有の特性)を相殺できる点にある。下の図で 交絡の強さ を動かすと、1 時点分・個体を区別しない「横断面 OLS」は交絡でどんどん偏るのに対し、同じ個体の個体内変動だけを使う「固定効果」推定は真の効果 β=2 に張り付いたまま動かないことが体感できる。


横断面 OLS(個体を区別しない)
真の効果 β=2 からの誤差:
固定効果(個体内変動のみ)
真の効果 β=2 からの誤差:
交絡の実現値
corr(特性,処置)=
真の効果 β = 2.00(固定)

図の各色は 6 個体、各点はその個体の 4 時点の観測。色ごとの追跡線(個体内の傾き)はどれも真の効果 β=2 に近い一方、個体の点群を横断的に結ぶと交絡で急な右上がりの見かけの関係が生まれる。横断面 OLS(赤)はこの見かけに引きずられて偏り、固定効果(緑)は個体内変動だけを見るので真の効果線(灰破線)にほぼ重なる。k=0(交絡なし)では両者が一致することも確認できる。計算はブラウザ内で実データ点から最小二乗・within 推定を厳密に実行している。

💡 直感:個体固有の差を「引き算」で消す

才能・地域の気風・古くからの制度など、観測できないが時間で変わらない個体固有の要因が処置と結果の両方に効いていると、単純な比較は「個体差」を「処置の効果」と取り違える。ところが同じ個体を繰り返し観測し、その個体の平均値を引いて個体内の動きだけを取り出すと、時間不変の要因は各観測から丸ごと相殺される。残るのは「同じ個体が時間とともにどう動いたか」だけ——観測されない差が消え、処置そのものの効果に近づく。これがパネルデータが横断面データにない交絡除去の力を持つ理由であり、固定効果モデル差分の差分 (DiD) はいずれもこの「個体内で引き算」を土台にしている。

⚠️ よくある落とし穴

🚀 発展

🧭 さらに深掘り — パネル因果推論の直感・落とし穴・発展

上の 🎮 デモと重ならない角度から、パネル因果推論をもう一段深く掘り下げる。「なぜ固定効果が交絡を消せるのか」「どこまで消せないのか」「近年の推定量は何を直したのか」を、実データ(SSDSE-B-2026)の数値とともに整理する。ここは追記解説であり、既存セクションの内容は変更していない。

🎨 直感 — 個体を「自分自身」と比べる

パネルデータ(個体 × 時間)の核心は、各個体を「別の個体」ではなく「過去・未来の自分自身」と比べる点にある。個体 $i$ の関係式 $Y_{it}=\beta X_{it}+\alpha_i+u_{it}$ から個体平均 $\bar{Y}_i=\beta\bar{X}_i+\alpha_i+\bar{u}_i$ を引くと、時間で変わらない個体固有項 $\alpha_i$ が丸ごと消える(within 変換)。

$$Y_{it}-\bar{Y}_i=\beta\,(X_{it}-\bar{X}_i)+(u_{it}-\bar{u}_i)$$

才能・県民性・古い制度・地理といった「観測できないが時間不変」の交絡は、それが $X$ とどれだけ相関していても、この引き算で自動的に統制される。差分の差分 (DiD) は「2 群 × 前後」という固定効果の特殊形、回帰不連続 (RDD)操作変数法 と並ぶ準実験(自然実験デザインの一角を占める。固定効果モデルパネルデータ が土台、交絡 の統制がねらいだ。

🧮 実データで体感(SSDSE-B-2026, cp932, skiprows=[1], 47県 × 2012–2023 = 564観測):高齢化率(A1303 / A1101 × 100)を説明変数、消費支出 L3221(円)を目的変数として、推定法だけを替えると係数がどう動くかを実測した。同一データ・同一変数で、横断的なプールド OLS と個体内で比べる固定効果が正反対の像を与えることに注目してほしい。
推定法高齢化率の係数(円/%pt)標準誤差t 値読み方
プールド OLS(横断的・個体を区別しない)-729.6284.4(iid)-2.57見かけ上「有意に負」
個体固定効果(within・県クラスタ SE)+8.9416.30.02ほぼ 0・非有意
二元固定効果 TWFE(県+年・県クラスタ SE)-692.41162.1-0.60非有意

解釈:プールド OLS が示す「高齢化率が高い県ほど消費支出が低い(-729.6 円/%pt、t=-2.57)」は、高齢で低消費という県の顔ぶれ(時間不変の県特性)を拾っただけの疑いが濃い。個体固定効果で県ごとの水準差を除くと係数は +8.9 まで縮み、t=0.02 と実質ゼロになる。「横断的な有意」が固定効果で消える——これが時間不変の交絡除去の威力の実測例だ。ただしこれは観察データの記述であり、並行トレンド等の識別条件を満たした厳密な因果効果ではない点に注意(教育目的の例示)。

⚠️ 落とし穴(重要)— 固定効果が消せないもの

💡 架空の数値例(TWFE 負の重み):架空の 3 県(早期処置・後期処置・未処置)で、真の処置効果がどの県でも $+5$ でも、効果が時間とともに $+2 \to +5 \to +8$ と成長する場合、段階的 TWFE 回帰は「早期処置県のあとの期を後期処置県の対照に使う」ため一部の比較に負の重みが乗り、係数が $+5$ を下回る(極端な設定では負になる)ことがある。数値は説明用の架空値であり実データではない。回避には Callaway–Sant'Anna 等の群・時点別推定量を使う。

🚀 発展 — 新しい推定量と設計

関連:パネルデータ固定効果モデル変量効果モデルHausman 検定差分の差分並行トレンド仮定操作変数法回帰不連続内生性交絡選択バイアス自己相関自然実験(因果推論の全体像は 因果と相関 も参照)。