論文中に 「内生性」として登場する用語。
内生性 とは:説明変数が誤差項と相関している問題。OLS推定がバイアスを持つ。操作変数法等で対処。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # 基本パターン import pandas as pd import numpy as np from scipy import stats import matplotlib.pyplot as plt import seaborn as sns # データ読み込み df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') # 基本統計量 df.describe() # 可視化 sns.pairplot(df[['食料費', '教育費', '住居費']]) plt.show() |
このページの上にある3つの概念マップ(関係マップ、 包含マップ、 ツリーマップ)でこの概念の位置づけが視覚的に分かります。 関連手法を辿って学習を進めましょう。
統計データ活用コンペティションのSSDSE-B-2026データは、 47都道府県の社会経済データ。 この概念を使って以下のような分析ができます:
| 機能 | Python (pandas) | Python (scipy) |
|---|---|---|
| 要約統計 | df.describe() | stats.describe() |
| 平均 | df.mean() | np.mean() |
| 標準偏差 | df.std() | np.std() |
| 相関 | df.corr() | stats.pearsonr() |
| t検定 | — | stats.ttest_ind() |
| 回帰 | — | stats.linregress() |
| 分布フィッティング | — | stats.norm.fit() |
この概念は、 他の多くの統計概念と密接に関連しています。 ジャストインタイム型学習では、 必要に応じて関連用語へジャンプしながら全体像を構築します。
| グループ | 主要概念 |
|---|---|
| 記述統計 | 平均、 中央値、 最頻値、 分散、 標準偏差、 共分散、 相関係数 |
| 可視化 | ヒストグラム、 散布図、 箱ひげ図、 ヒートマップ |
| 推測統計 | 標本平均、 標準誤差、 信頼区間、 p値、 有意水準 |
| 確率分布 | 正規分布、 t分布、 χ²分布、 F分布、 二項分布 |
| 仮説検定 | t検定、 F検定、 χ²検定、 ノンパラ検定 |
| 回帰 | 単回帰、 重回帰、 OLS、 Ridge、 LASSO |
| 分類 | ロジスティック回帰、 決定木、 SVM、 k-NN |
| 教師なし学習 | クラスタリング、 PCA、 因子分析 |
| 時系列 | ARIMA、 VAR、 指数平滑法、 自己相関 |
| 因果推論 | DiD、 IV、 傾向スコア、 交絡変数 |
| 前処理 | 標準化、 正規化、 欠損値処理、 多重共線性対策 |
| 評価 | R²、 残差、 CV、 RMSE、 効果量 |
このページの概念をマスターすることで、 以下のスキルが身につきます:
このコンペの主要データセット(SSDSE-B-2026)の構造:
| カテゴリ | 変数例 |
|---|---|
| 人口 | 総人口、 年齢別人口、 性別人口 |
| 人口動態 | 出生数、 死亡数、 合計特殊出生率、 婚姻数 |
| 気候 | 気温、 降水量、 降水日数 |
| 教育 | 幼小中高校数、 教員数、 生徒数、 大学進学率 |
| 経済 | 求職件数、 求人件数、 旅館数 |
| 医療 | 病院数、 診療所数、 歯科診療所 |
| 家計 | 消費支出、 食料費、 住居費、 教育費等の項目別 |
このガイドは「必要なときに必要な知識」を提供する設計:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # 必須ライブラリのインストール pip install pandas numpy scipy statsmodels scikit-learn matplotlib seaborn # 標準的なインポート import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_squared_error # 日本語表示の設定(matplotlib) plt.rcParams['font.family'] = 'Hiragino Sans' plt.rcParams['axes.unicode_minus'] = False # データ読み込み(SSDSE は cp932 エンコーディング) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932') print(df.shape) print(df.head()) print(df.describe()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | def quick_eda(df, target=None): """探索的データ分析の基本テンプレート""" print(f"Shape: {df.shape}") print(f"\nColumn types:\n{df.dtypes}") print(f"\nMissing values:\n{df.isnull().sum()}") print(f"\nBasic stats:\n{df.describe()}") # 数値列の可視化 numeric_cols = df.select_dtypes(include=[np.number]).columns df[numeric_cols].hist(bins=20, figsize=(15, 10)) plt.tight_layout() plt.show() # 相関ヒートマップ if len(numeric_cols) > 1: plt.figure(figsize=(12, 10)) sns.heatmap(df[numeric_cols].corr(), annot=True, fmt='.2f', cmap='RdBu_r', center=0) plt.show() # ターゲットがあれば散布図行列 if target and target in df.columns: sns.pairplot(df[numeric_cols[:5]], hue=target if df[target].dtype == 'O' else None) plt.show() |
分析結果を報告する際の標準的な構成:
p値だけでなく効果量も併記するのが現代統計の標準。 主要な指標と Cohen の解釈基準:
| 統計量 | 効果量 | 小 | 中 | 大 |
|---|---|---|---|---|
| 2群平均差 | Cohen's d | 0.2 | 0.5 | 0.8 |
| 相関 | r | 0.1 | 0.3 | 0.5 |
| 線形回帰 | R² | 0.02 | 0.13 | 0.26 |
| ANOVA | η² (eta²) | 0.01 | 0.06 | 0.14 |
| χ² | Cramér's V | 0.1 | 0.3 | 0.5 |
| ロジスティック | Odds Ratio | 1.5 | 2.5 | 4.0 |
内生性 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 体系階層に未登録
中心の概念から放射状に、 前提・兄弟・発展形・応用先などの関係性を矢印で結びます。 横の繋がりを見るのに最適。 ノードをドラッグ、 ホイールでズーム、 クリックで遷移。
大きな円が小さな円を包含する Circle Packing 図。 「内生性」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「内生性」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 ジャストインタイム学習のヒント:3つの視点を行き来することで、 概念を多角的に理解できます。 包含マップやツリーマップはズーム/ドリルダウンで大分類から細部まで探索できます。
内生性(endogeneity)に関する用語を、 原因のタイプ・解決手法・診断 別に索引化します。
| カテゴリ | キーワード(日本語) | キーワード(英語) |
|---|---|---|
| 原因のタイプ | 欠落変数バイアス、 同時性バイアス、 測定誤差、 自己選択、 サンプルセレクション | omitted variable bias, simultaneity, measurement error, self-selection |
| 解決手法 | 操作変数法、 2段階最小二乗法、 差の差分析、 回帰不連続デザイン、 固定効果 | IV, 2SLS, DiD, RDD, fixed effects, GMM |
| 診断・検定 | Hausman検定、 弱操作変数、 過剰識別検定(Sargan)、 Cragg-Donald F | Hausman test, weak IV, Sargan, Cragg-Donald, J-statistic |
| 因果推論 | 処置効果、 平均処置効果(ATE)、 反実仮想、 ランダム化 | treatment effect, ATE, counterfactual, randomization |
| 関連概念 | 外生性、 共変量、 交絡変数、 識別、 反応バイアス | exogeneity, confounder, identification, response bias |
| 実装ライブラリ | linearmodels、 statsmodels、 econtools、 doWhy、 EconML | linearmodels.iv.IV2SLS, statsmodels, doWhy, EconML |
SSDSE-B から「都道府県別の教育支出(万円/世帯)」と「平均所得(万円)」のデータを使い、 内生性の問題を考えます。
所得 = β₀ + β₁ × 教育支出 + ε
OLSで推定すると β₁ ≈ +12(教育支出1万円増で所得12万円増)
しかし、 これは因果的な解釈ができない。 なぜなら:
・所得が高い世帯ほど教育支出を増やす(逆因果・同時性)
・親の学歴という欠落変数が両方に影響する
・教育支出には測定誤差が含まれる(自己申告)
| IV候補 | 関連性 | 外生性 |
|---|---|---|
| 都道府県の大学数 | 教育支出と相関あり ◯ | 所得に直接効果なしと仮定 △ |
| 義務教育延長改革(コホート) | 教育年数を強制的に変える ◎ | 所得には間接効果のみ ◎ |
| 家庭の蔵書数 | 弱い △ | 直接効果あり懸念 ✕ |
第1段階:教育支出 ̂ = γ₀ + γ₁ × 大学数 + u
第2段階:所得 = β₀ + β₁ × 教育支出 ̂ + ε
結果:β₁ ≈ +5(OLSの+12より小さい)
OLSが 過大評価 していたことが判明。 反事実が考慮された因果的推定値。
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd from linearmodels.iv import IV2SLS df = pd.read_csv('data/raw/SSDSE-B-2024.csv', encoding='shift_jis', skiprows=1) # 所得 = β × 教育支出 + ε IV: 大学数 y = df['平均所得'] exog = pd.DataFrame({'const': 1}, index=df.index) endog = df[['教育支出']] instruments = df[['大学数']] mod = IV2SLS(y, exog, endog, instruments).fit() print(mod.summary) print('第1段階F:', mod.first_stage.diagnostics) |
1 2 3 4 5 6 7 8 9 | import statsmodels.api as sm import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2024.csv', encoding='shift_jis', skiprows=1) X = sm.add_constant(df[['教育支出']]) y = df['平均所得'] ols = sm.OLS(y, X).fit() print(ols.summary()) # OLS推定値(バイアスあり可能性) |
1 2 3 4 | from linearmodels.iv import compare # OLS と 2SLS の結果を比較 print(compare({'OLS': ols_result, '2SLS': iv_result})) # Wu-Hausman統計量 p<0.05 なら内生性あり → IVを採用 |
1 2 3 4 5 6 7 8 9 10 | from econml.iv.dml import DMLIV from sklearn.ensemble import RandomForestRegressor est = DMLIV( model_y=RandomForestRegressor(n_estimators=100), model_t=RandomForestRegressor(n_estimators=100), model_t_xwz=RandomForestRegressor(n_estimators=100) ) est.fit(Y=df['平均所得'], T=df['教育支出'], Z=df['大学数'], X=df[['人口']]) print('CATE推定:', est.const_marginal_effect(df[['人口']])) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import dowhy from dowhy import CausalModel model = CausalModel( data=df, treatment='教育支出', outcome='平均所得', common_causes=['人口'], instruments=['大学数'] ) identified = model.identify_effect() estimate = model.estimate_effect(identified, method_name='iv.instrumental_variable') print(estimate) # 反証検証 refute = model.refute_estimate(identified, estimate, method_name='placebo_treatment_refuter') print(refute) |
「内生性」を理解するうえで必要なキーワードを 10 件以上提示します。 各チップから対応セクションへ移動できます。
30 秒結論 文脈 直感 数式 記号読み解き 実値計算 Python 実装 落とし穴 関連手法 関連用語 グループ教材 概念マップ
このセクションは「内生性」を扱う 用語ページ です。 統計データ分析コンペティション(2026)の再現教材における中核用語のひとつで、教育投資 (L322107) と所得 (A4101 課税対象所得) の同時決定性 という観点で SSDSE-B-2026(47 都道府県 × 複数年 × 100 超列)に紐づけられます。
位置づけ:相関・線形回帰・仮説検定 といった基礎用語群と並列であり、応用としては 内生性・IV・DID・クラスタリング 等へ繋がります。
内生性 を一言でいえば「教育投資 (L322107) と所得 (A4101 課税対象所得) の同時決定性」。 47 都道府県という小さな母集団でも、 SSDSE-B-2026 の L322107 列に注目すると、 大都市圏と地方の差・人口規模に伴う相対比較など、 様々なパターンが見えてきます。
比喩でいうと、 内生性 はデータ分析の「眼鏡」のようなもの。 同じデータでも眼鏡を変えれば、 平均(中心)・分散(ばらつき)・相関(連動)・因果(影響)と、 異なる情報が浮かび上がります。 SSDSE-B-2026 を題材に、 この眼鏡をかけてみるのが本ページの狙いです。
内生性 の代表的な定義式は次のとおりです。
$$ Y = \beta_0 + \beta_1 X + u, \quad \mathrm{Cov}(X, u) \neq 0 \;\Rightarrow\; \hat{\beta}_{1,\text{OLS}} \text{ は不偏でない} $$ここで使われる記号や演算の意味は次節で言葉に翻訳します。
数式の各記号を、日本語の意味に変換します。
SSDSE-B-2026(公的統計の社会・教育系データセット、 47 都道府県 × 10 年分超 × 100 以上の列)を用いて、 「内生性」を体感します。 ファイル名は SSDSE-B-2026.csv、 読み込みは下記の Python コードで行います。
import pandas as pd
# SSDSE-B-2026 を読み込む(cp932 / Shift_JIS)
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')
print(df.shape) # (564, 112)
print(df['SSDSE-B-2026'].unique()) # 含まれる年度
latest = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()
print(latest[['Prefecture', 'L322107', 'A4101']].head())
ここで使った中心列 L322107 は SSDSE-B-2026 における 教育投資 (L322107) と所得 (A4101 課税対象所得) の同時決定性 に関連する指標です。 算出例:
L322107 平均と標準偏差を求めるL322107 と A4101 の相関(線形・順位)を比較するscipy / pandas / scikit-learn / statsmodels を中心とした標準的な実装例です。 まず CSV を読み込み、 次に 内生性 の解析を行います。
import pandas as pd
import numpy as np
from scipy import stats
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', skiprows=[1], encoding='cp932')
df = df[df['SSDSE-B-2026'] == df['SSDSE-B-2026'].max()].copy()
x = df['L322107'].astype(float).values
y = df['A4101'].astype(float).values
# 基本統計量
print('n =', len(x))
print('mean(x) =', np.mean(x))
print('std(x) =', np.std(x, ddof=1))
# 内生性 の代表的計算(用途に応じて scipy/statsmodels を切替える)
r, p = stats.pearsonr(x, y)
print(f'Pearson r = {r:.4f}, p = {p:.4g}')
rs, ps = stats.spearmanr(x, y)
print(f'Spearman rho = {rs:.4f}, p = {ps:.4g}')
用途別の追加実装:
# 標準化と簡易クラスタリングの例
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
X = df[['L322107', 'A4101']].astype(float).values
Xs = StandardScaler().fit_transform(X)
km = KMeans(n_clusters=4, n_init=10, random_state=0).fit(Xs)
df['cluster'] = km.labels_
print(df[['Prefecture', 'L322107', 'A4101', 'cluster']].head(10))
# 時系列(北海道の L322107)— 例として ARIMA 系の前処理
import statsmodels.api as sm
ts = df.sort_values('SSDSE-B-2026').groupby('SSDSE-B-2026')['L322107'].mean()
print(ts.tail())
res = sm.tsa.stattools.adfuller(ts)
print('ADF stat:', res[0], 'p:', res[1])
内生性 を実務で扱う際に踏みやすい落とし穴を 5 件挙げます。
本ページでは「内生性」を 12 セクション(🔖 キーワード索引/💡 30 秒結論/📍 文脈/🎨 直感/📐 数式/🔬 記号読み解き/🧮 実値計算/🐍 Python 実装/⚠️ 落とし穴/🌐 関連手法/🔗 関連用語/📚 グループ教材)で完結に整理しました。 SSDSE-B-2026 を素材に、 概念の輪郭・式の意味・実装手順・典型的な失敗パターンの 4 点を最低限押さえれば、 統計データ分析コンペの現場で迷わず使えるはずです。
「説明変数 X が誤差項 ε と相関する」内生性は、 抽象的に聞こえますが具体的な原因は 3 つに分類できます。 SSDSE-B-2026(47 都道府県データ)の文脈で、 それぞれをどう識別し、 どう対処するかを整理します。
| 原因 | メカニズム | SSDSE-B での例 | 主な対処 |
|---|---|---|---|
| 欠落変数バイアス | 真に重要な変数がモデルから外れている | 「教育投資 → 経済成長」を分析する際、 「初期所得」を入れないと過大評価 | 変数追加、 固定効果 |
| 逆因果(同時性) | Y → X の方向も同時に存在 | 「医療費 ↔ 高齢化率」は両方向的 | 操作変数法、 DiD |
| 測定誤差 | X が真の値ではなく観測値(ノイズあり) | 「世帯所得」のアンケート申告値はバイアスあり | 操作変数法、 SEM |
import pandas as pd
from linearmodels.iv import IV2SLS
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['一般世帯数', '人口(総数)', '15歳未満人口', '65歳以上人口'])
# OLS
import statsmodels.api as sm
X_ols = sm.add_constant(df[['一般世帯数']])
ols = sm.OLS(df['人口(総数)'], X_ols).fit()
# IV:操作変数として 15 歳未満人口を使う想定
iv_model = IV2SLS.from_formula(
'人口(総数) ~ 1 + [一般世帯数 ~ 15歳未満人口] + 65歳以上人口',
data=df
).fit()
print('OLS β:', round(ols.params['一般世帯数'], 4))
print('IV β :', round(iv_model.params['一般世帯数'], 4))
# 両者が大きく異なれば内生性の疑い大
print(iv_model.wu_hausman()) # p 値 < 0.05 で内生性あり
| 手法 | 必要な条件 | SSDSE-B での適用可能性 |
|---|---|---|
| 操作変数法 (IV) | IV が外生かつ X と相関 | 地理的・歴史的変数を IV にできる |
| DiD | 処置と非処置、 前後パネル | 時系列パネル化が必要 |
| 固定効果 | 時間不変な交絡のみ | パネル化すれば適用可能 |
| RDD | 処置がスコアで決まる | 行政区分が境界 → 適用余地 |
| 傾向スコア | 観測される交絡のみ | 県特性で重み付け可能 |
💡 判断フロー:(1) 理論的に内生性を疑うべき変数を特定 → (2) Hausman / Wu 検定で統計的に確認 → (3) 利用可能なデータ構造(パネル? 自然実験?)に応じた手法選択 → (4) 結果を OLS と比較し感度分析、 が王道です。
実務で内生性に対処するときは、 単一手法に頼らず 5 段階のパイプライン で取り組むのが定石です。 SSDSE-B-2026 を題材に、 各ステップで何をするかを整理します。
| Step | 作業 | 出力物 |
|---|---|---|
| 1 | 因果図(DAG)作成 | 変数間の前提を明示 |
| 2 | 内生性源の同定 | 欠落 / 逆因果 / 測定の分類 |
| 3 | 手法選択 | IV / DiD / FE / 傾向スコア |
| 4 | 推定 + 検定 | Hausman / 弱操作変数検定 |
| 5 | 感度分析 | 未観測交絡への耐性 |
SSDSE-B-2026 で「教育投資 X が経済指標 Y に与える効果」を検証する場合、 仮定する因果関係を DAG で描いておくと、 後続の手法選択が機械的に決まります。
# 例:DAG を networkx で可視化
import networkx as nx
import matplotlib.pyplot as plt
G = nx.DiGraph()
edges = [
('教育投資', '経済指標'), # 主要因果
('高齢化率', '教育投資'), # 交絡
('高齢化率', '経済指標'), # 交絡
('初期所得', '教育投資'), # 交絡 + 逆因果
('初期所得', '経済指標'),
]
G.add_edges_from(edges)
pos = nx.spring_layout(G, seed=1)
nx.draw(G, pos, with_labels=True, node_color='#FFCDD2', node_size=2400, font_size=11)
plt.title('DAG: 教育投資と経済指標の因果')
plt.show()
操作変数法(IV)は「強い操作変数」を前提とします。 弱い IV では推定がかえって悪化するため、 第 1 段階の F 統計量で IV の強さを確認します(経験則:F > 10)。
import pandas as pd, statsmodels.api as sm
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['一般世帯数', '15歳未満人口', '人口(総数)'])
# 第 1 段階:内生変数 を IV で回帰
first_stage = sm.OLS(df['一般世帯数'],
sm.add_constant(df[['15歳未満人口']])).fit()
F_stat = first_stage.fvalue
print(f'第 1 段階 F 統計量: {F_stat:.1f}')
print('F > 10 なら IV は強い(経験則)')
# SSDSE-B では世帯数と若年人口は強く相関するので F >> 10
観測されない交絡が結果を反転させるには「どれくらい強い必要があるか」を計算するのが感度分析です。 E-value(VanderWeele & Ding, 2017)は「観察された関連を説明するために必要な未観測交絡の強さ」を 1 つの数字で要約します。
# 簡易 E-value 計算(リスク比 RR の場合)
RR = 2.5 # 推定された処置効果
E_value = RR + (RR * (RR - 1)) ** 0.5
print(f'E-value: {E_value:.2f}')
# 解釈:未観測交絡が処置・結果と E-value 倍以上関連していないと結果は説明できない
💡 論文への教訓:因果推論の論文は「手法が正しい」だけでなく「前提が明示されている」が査読の通過点。 DAG と感度分析を併記するだけで査読者の信頼が上がります。
内生性(endogeneity)は、 計量経済学が「観測データから因果関係を読み取る」ために闘ってきた最大の難題の一つです。 その歴史を概観すると、 現代の因果推論手法が「なぜそうあるか」が見えてきます。
| 年 | 手法・出来事 | 代表的論文・人物 |
|---|---|---|
| 1928 | Wright が操作変数法 (IV) を提唱 | Philip Wright |
| 1944 | Haavelmo が「確率的アプローチ」を確立 | Trygve Haavelmo (1989 ノーベル賞) |
| 1958 | 2SLS の体系化 | Theil, Basmann |
| 1978 | Hausman 検定の登場 | Jerry Hausman |
| 1994 | Card & Krueger の DiD 古典 | 最低賃金研究 |
| 2000s | 「クレディビリティ革命」 | Angrist, Imbens, Pischke |
| 2021 | 自然実験の方法でノーベル賞 | Card, Angrist, Imbens |
内生性に対処する手法は大きく 4 つの系統に分かれます。 それぞれの強み・弱みを整理しておくと、 実データに直面したときの選択肢がクリアになります。
| 系統 | 代表手法 | 必要なデータ構造 | 主な仮定 |
|---|---|---|---|
| 操作変数 | IV, 2SLS, LIML, GMM | 外生な IV 変数 | 関連性と除外制約 |
| 準実験 | DiD, RDD, 合成統制法 | 処置タイミング, 閾値 | 共通トレンド等 |
| パネル | 固定効果, ランダム効果 | パネルデータ | 時間不変交絡のみ |
| マッチング | 傾向スコア, NN マッチング | 処置と対照群 | 条件付き独立性 |
import pandas as pd
import statsmodels.api as sm
from linearmodels.iv import IV2SLS
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['人口(総数)', '一般世帯数', '15歳未満人口', '65歳以上人口'])
# 1. ナイーブ OLS
X1 = sm.add_constant(df[['一般世帯数']])
ols = sm.OLS(df['人口(総数)'], X1).fit()
print(f'OLS: β = {ols.params["一般世帯数"]:.4f}')
# 2. 制御変数追加(観測される交絡を補正)
X2 = sm.add_constant(df[['一般世帯数', '65歳以上人口']])
ols2 = sm.OLS(df['人口(総数)'], X2).fit()
print(f'OLS + 制御: β = {ols2.params["一般世帯数"]:.4f}')
# 3. 操作変数法(15歳未満人口を IV と仮定)
iv = IV2SLS.from_formula(
'人口(総数) ~ 1 + [一般世帯数 ~ 15歳未満人口] + 65歳以上人口',
data=df
).fit()
print(f'IV (2SLS): β = {iv.params["一般世帯数"]:.4f}')
# 3 手法で β がどう変わるか比較
# 大きく変わるなら内生性の疑い濃厚
2000 年代以降、 IV の最大の問題は「弱い操作変数」であることが明らかになりました。 関連性が弱い IV を使うと、 OLS バイアスを直す前にもっと大きな 2SLS バイアス が生じます。 Stock-Yogo (2005) の表で、 必要な第 1 段階 F 統計量を確認するのが標準的です。
| IV の数 | Stock-Yogo 臨界値(10% バイアス) |
|---|---|
| 1 | 16.4 |
| 2 | 19.9 |
| 3 | 22.3 |
💡 論文掲載時の必須項目:(1) 第 1 段階の F 統計量、 (2) 過識別検定(Sargan / Hansen J)、 (3) Hausman 検定、 (4) 感度分析(E-value 等)。 これらを併記してはじめて、 査読を通る因果推論論文になります。
「内生性 = OLS にバイアス」を抽象的に言うだけでなく、 SSDSE-B-2026 + ノイズ混入で実際に観察してみましょう。 真のパラメータが既知の状況を作り、 OLS と IV の推定がどれくらいズレるかを比較します。
import pandas as pd, numpy as np
import statsmodels.api as sm
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1)
df = df.dropna(subset=['一般世帯数', '15歳未満人口'])
# SSDSE データから X, IV を取り、 内生性を人工的に混入
np.random.seed(0)
n = len(df)
true_beta = 2.0
# 説明変数 X が誤差 ε と相関する形を作る
eps = np.random.normal(0, 1, n)
X_endo = df['一般世帯数'].values / 1e5 + 0.7 * eps # 内生
y = true_beta * X_endo + eps # ε との相関がバイアス源
# 操作変数(外生):15歳未満人口
Z = df['15歳未満人口'].values / 1e5
# OLS
ols_beta = np.linalg.lstsq(X_endo.reshape(-1, 1), y, rcond=None)[0][0]
# 2SLS
X_hat = np.polyval(np.polyfit(Z, X_endo, 1), Z)
iv_beta = np.linalg.lstsq(X_hat.reshape(-1, 1), y, rcond=None)[0][0]
print(f'真の β: {true_beta:.4f}')
print(f'OLS β: {ols_beta:.4f} (バイアス {ols_beta - true_beta:+.4f})')
print(f'IV (2SLS): {iv_beta:.4f} (バイアス {iv_beta - true_beta:+.4f})')
# OLS は ε との相関でバイアス、 IV は外生な Z で補正
このように、 真のパラメータが既知のシミュレーションで OLS のバイアスの大きさを目で確認できます。 実データでは真の β が分からないため、 IV や DiD などの手法で「内生性を補正したら OLS とどれくらい違うか」を確認するのが定石です。
💡 教訓:内生性に気づかないまま OLS を使うと、 真の β から系統的に外れた結果を信じてしまいます。 シミュレーションで「バイアスはこんなに大きい」を体感しておくと、 実データでも警戒できます。