Mímisbrunnr知恵の泉

← 因果推論 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:バックドア基準と識別 | 識別の仮定

要点(BLUF)


1. 全体像:意思決定フロー

flowchart TB
    Q["1. 因果の問いを定義(処置・結果・対象集団・推定対象 ATE/ATT)"] --> D["2. DAG を描く(交絡・媒介・合流点を区別)"]
    D --> A["3. 識別の仮定を点検(交換可能性・正値性・SUTVA)"]
    A --> R{"処置をランダム化できる?"}
    R -->|"はい"| RCT["RCT(黄金律)"]
    R -->|"いいえ"| OBS{"交絡を十分に観測できる?"}
    OBS -->|"はい"| BD["4. バックドア基準で調整集合を選ぶ → 5. 回帰・傾向スコア・AIPW"]
    OBS -->|"いいえ"| QE["5. 準実験デザイン(IV・DID・RDD・SCM)"]
    RCT --> S["6. 感度分析・頑健性(E値・プラセボ・バンド幅)"]
    BD --> S
    QE --> S
    S --> REP["7. 結論を仮定とセットで報告"]

このフローの背骨は**「識別 → 推定」の順序**だ。先にどんな仮定なら因果と言えるか(識別)を固め、その後で初めてどう数値を出すか(推定)に進む。順序を逆にすると、立派な推定値が出ても何を測ったのか言えなくなる。


2. 各ステップの中身

  1. 問いの定義:処置 XX・結果 YY・対象集団・推定対象(全体の平均効果 ATE か、処置群での効果 ATT か)を一文で書く。「効果はあるか」ではなく「誰に対する、何から何への効果か」まで具体化する。
  2. DAG を描く因果ダイアグラムとd分離):交絡・媒介・合流点・処置後変数を区別する。変数の役割は相関では決まらない――背景知識で構造を描く。
  3. 識別の仮定の点検識別の仮定):
    • 交換可能性:観測した CC で層別すれば処置はランダムと見なせるか(=未観測交絡がないか)。検証不能なので⑥の感度分析で補う。
    • 正値性(重なり):どの共変量値でも処置群・対照群の両方が存在するか(0<e(C)<10<e(C)<1)。傾向スコアの分布で点検する。
    • SUTVA:他人の処置が自分の結果に影響しない(干渉なし)・処置に隠れた多バージョンがない。
  4. 調整集合の選択バックドア基準と識別):DAG 上で XYX\to Y 以外のバックドアパスをすべて塞ぐ最小集合を選ぶ。媒介・合流点・その子孫は入れない
  5. 推定法の選択:ランダム化できればRCT。交絡を十分観測できれば回帰・傾向スコア・AIPW。未観測交絡が残るなら準実験デザイン(IV・DID・RDD・SCM)。
  6. 感度分析未観測交絡への感度分析):未観測交絡なら E値、準実験ならプラセボ検定やバンド幅依存性など、残った仮定が崩れたときの揺らぎを定量化する。
  7. 報告:点推定と区間に加え、置いた仮定・調整集合・感度分析をセットで述べる。仮定を書かない因果主張は不完全。

3. 実証:チェックリストを擬似例に通す

交絡 CC・媒介 MM・合流点 KK を仕込んだデータを作る。CC は処置前の交絡(入れるべき)、MM は媒介(入れてはいけない)、KK は処置後の合流点(入れてはいけない)。真の総効果は 2.72.7。チェックリスト④(正値性点検)と⑤(調整集合の比較)を回す。

import numpy as np
import statsmodels.api as sm
from sklearn.linear_model import LogisticRegression

# === チェックリストを1つの擬似例に適用して通す ===
rng = np.random.default_rng(2026)
n = 40_000

C = rng.normal(0, 1, size=n)                       # 観測された交絡
X = rng.binomial(1, 1 / (1 + np.exp(-C)))          # 処置(C→X)
M = 1.2 * X + rng.normal(0, 1, size=n)             # 媒介(X→M)
tau_direct = 1.5
Y = 1.0 * M + tau_direct * X + 2.0 * C + rng.normal(0, 1, size=n)
K = X + Y + rng.normal(0, 1, size=n)               # 合流点(X→K←Y)
ATE_total_true = 1.0 * 1.2 + tau_direct            # 総効果 = 2.7

# --- 正値性の点検: 傾向スコアが0/1に張り付いていないか ---
e_hat = LogisticRegression().fit(C.reshape(-1, 1), X).predict_proba(C.reshape(-1, 1))[:, 1]
print(f"傾向スコアの範囲 = [{e_hat.min():.3f}, {e_hat.max():.3f}](0/1 から離れていれば正値性OK)")

def x_coef(cols):
    design = sm.add_constant(np.column_stack(cols))
    return sm.OLS(Y, design).fit().params[1]

print(f"\n真の総効果 = {ATE_total_true:.2f}\n")
print(f"調整なし          Y~X      : {x_coef([X]):.3f}(交絡 C を無視 → 偏る)")
print(f"正しい調整(Cのみ)  Y~X+C    : {x_coef([X, C]):.3f}(バックドア基準 → 総効果を回収)")
print(f"媒介Mも調整        Y~X+C+M  : {x_coef([X, C, M]):.3f}(過剰調整 → 直接効果に縮む)")
print(f"合流点Kも調整      Y~X+C+K  : {x_coef([X, C, K]):.3f}(合流点バイアス → 偏る)")

出力は次の通り。

傾向スコアの範囲 = [0.018, 0.982](0/1 から離れていれば正値性OK)

真の総効果 = 2.70

調整なし          Y~X      : 4.344(交絡 C を無視 → 偏る)
正しい調整(Cのみ)  Y~X+C    : 2.710(バックドア基準 → 総効果を回収)
媒介Mも調整        Y~X+C+M  : 1.500(過剰調整 → 直接効果に縮む)
合流点Kも調整      Y~X+C+K  : 0.247(合流点バイアス → 偏る)

出力の意味――まず正値性の点検。傾向スコアの範囲は [0.018,0.982][0.018, 0.982]0/10/1 に張り付いておらず、重なりは良好(チェックリスト③合格)。次に調整集合の比較:

同じデータ・同じ推定法(OLS)でも、調整集合の選び方ひとつで結論が 0.250.25 から 4.34.3 まで動く。正しい答え 2.72.7 を当てたのは「DAG が指す CC だけを入れた」ときだけ。チェックリストの④(バックドア基準)がいかに決定的かが分かる。


4. 報告:結論は仮定とセットで

推定値を出したら、感度分析(未観測交絡への感度分析)まで添えて初めて誠実な因果主張になる。最低限の報告セットは:


⚠️ よくある誤解・落とし穴


関連ノート