Mímisbrunnr知恵の泉

← 因果推論 一覧

🎓 レベル:発展 | 重要度:B(標準)

📎 前提:因果ダイアグラムとd分離 | バックドア基準と識別 | 関連:反事実とPearlの因果の階梯

要点(BLUF)


1. SCM:DAG に生成メカニズムを与える

構造的因果モデル(Structural Causal Model, SCM) は4つ組 U,V,F,P(U)\langle U, V, F, P(U)\rangle です。

Vi  =  fi(pa(Vi), Ui)V_i \;=\; f_i\big(\mathrm{pa}(V_i),\ U_i\big)

たとえば交絡 CC・処置 XX・結果 YY なら、次の3本が SCM です。

C=UC,X=fX(C,UX),Y=fY(X,C,UY)C = U_C,\qquad X = f_X(C, U_X),\qquad Y = f_Y(X, C, U_Y)

この構造は下の DAG に対応します。CXC\to XCYC\to Y があるので、XCYX\leftarrow C\to Y というバックドアパスが開いています。

flowchart LR
    C["交絡 C"] --> X["処置 X"]
    C --> Y["結果 Y"]
    X --> Y

外生ノイズが独立なら、同時分布は親への条件付き分布の積に分解されます(因果マルコフ条件因果ダイアグラムとd分離)。

P(c,x,y)  =  P(c)P(xc)P(yx,c)P(c,x,y) \;=\; P(c)\,P(x\mid c)\,P(y\mid x,c)

DAG は「どの変数が誰の親か」だけを描き、SCM は「親からどう生成されるか(fif_iP(U)P(U))」まで指定します。do演算子の意味は、この生成メカニズムまで踏み込まないと定義できません


2. do演算子:構造方程式を1本だけ書き換える

知りたいのは「XX外から操作したら YY がどうなるか」です。SCM ではこれを、XX の構造方程式 X=fX(C,UX)X=f_X(C,U_X) を捨て、XX を定数 xx置き換えた新しいモデル MxM_x で定義します。

do(X=x):X=fX(C,UX)  X=xdo(X=x):\quad X=f_X(C,U_X)\ \longrightarrow\ X=x

グラフでいえば、XX入ってくる矢印をすべて切る操作です(CXC\to X が消える)。これをグラフ手術(graph surgery) と呼びます。残りの方程式はそのままなので、介入後の DAG は次のようになります。

flowchart LR
    C["交絡 C"] --> Y["結果 Y"]
    X["処置 X = x(外から固定)"] --> Y

この手術後のモデルが定める分布が介入分布 P(Ydo(X=x))P(Y\mid do(X{=}x)) です。同時分布は、XX の因子を落とした切断積(truncated product) になります。

P(c,ydo(X=x))  =  P(c)P(yx,c)P\big(c,y \mid do(X{=}x)\big) \;=\; P(c)\,P(y\mid x,c)

ここがポイントです。観測分布の分解 P(c)P(xc)P(yx,c)P(c)P(x\mid c)P(y\mid x,c) と比べると、P(xc)P(x\mid c) という「CCXX を選ぶ」因子だけが消えています。観測では「CC の高い人ほど X=1X{=}1 になりやすい」という相関が残るのに対し、dodoXX を外から決めるのでその相関が断たれるのです。YY を周辺化すると介入分布の核心式が出ます。

P(ydo(X=x))  =  cP(yx,c)P(c)P(y\mid do(X{=}x)) \;=\; \sum_c P(y\mid x,c)\,P(c)

これがまさにバックドア調整公式であり、バックドア基準と識別で潜在結果から導いた g公式と完全に一致します。観測の条件付き分布は対照的に

P(yX=x)  =  cP(yx,c)P(cx)P(y\mid X{=}x) \;=\; \sum_c P(y\mid x,c)\,P(c\mid x)

で、重みが P(c)P(c) ではなく P(cx)P(c\mid x) です。XXCC が相関する(P(cx)P(c)P(c\mid x)\neq P(c))かぎり、両者はズレます。これが「見る \neq する」の数式表現です。


3. do計算でバックドア調整を導く

Pearl のdo計算(do-calculus) は、dodo を含む式を変形するための3つの規則です。GXG_{\overline X} は「XX へ入る矢印を消したグラフ」、GXG_{\underline X} は「XX から出る矢印を消したグラフ」を表します。

これでバックドア調整を導きます。CC がバックドア基準を満たす(XX の非子孫で、すべてのバックドアパスを塞ぐ)とします。まず全確率の法則で CC を挟みます。

P(ydo(x))  =  cP(ydo(x),c)P(cdo(x))P(y\mid do(x)) \;=\; \sum_c P\big(y\mid do(x),c\big)\,P\big(c\mid do(x)\big)

第2因子に規則3を使います。CCXX の非子孫なので、XX に介入しても CC の分布は変わりません(GXG_{\overline X}CXC\perp X)。

P(cdo(x))  =  P(c)P(c\mid do(x)) \;=\; P(c)

第1因子に規則2を使います。CC がすべてのバックドアパスを塞ぐので、XX から出る矢印を消したグラフ GXG_{\underline X}YXCY\perp X\mid C。したがって do(x)do(x) を観測 xx に置き換えられます。

P(ydo(x),c)  =  P(yx,c)P(y\mid do(x),c) \;=\; P(y\mid x,c)

ふたつを戻すと、バックドア調整公式が出ます。

P(ydo(x))  =  cP(yx,c)P(c)P(y\mid do(x)) \;=\; \sum_c P(y\mid x,c)\,P(c)

潜在結果(潜在結果モデル)と SCM という別々の言語が、同じ識別公式に行き着く。これが因果推論の2大枠組みの一致点です。なお do計算は完全(complete) であることが知られ、「dodo を含む量が観測分布だけで書けるか(識別可能か)」は三規則の有限回適用で判定できます(フロントドア基準などバックドアで届かない識別もここから出ます。do計算の完全性は確立した結果です)。


4. 擬似データで「見る≠する」を数値化する

構造方程式から擬似データを作り、(1) 観測の条件付き差、(2) バックドア調整、(3) do介入のシミュレーションを比べます。交絡 CC を二値にして、バックドア調整公式を 2 項の和としてそのまま実装します。真の効果は ATE=2.0\text{ATE}=2.0 です。

import numpy as np

# === SCMから観測分布と介入分布を作り、do(X)が真の因果効果を当てることを確かめる ===
rng = np.random.default_rng(2026)
n = 40000
ATE_true = 2.0

# --- 構造方程式(SCM): 各変数を「親 + 外生ノイズ」で生成 ---
# 交絡 C(例:重症度。値1ほど治療されやすく、結果も底上げ)
C = rng.binomial(1, 0.5, size=n)

# 処置 X は C に依存(C=1なら受けやすい) ... これがバックドアパス X←C→Y
prob_treat = np.where(C == 1, 0.8, 0.2)
X = rng.binomial(1, prob_treat)

# 結果 Y = 1 + tau*X + 3*C + ノイズ
Y = 1.0 + ATE_true * X + 3.0 * C + rng.normal(0.0, 1.0, size=n)

# (1) 観測分布での素朴な条件付き差 E[Y|X=1]-E[Y|X=0]
naive = Y[X == 1].mean() - Y[X == 0].mean()

# (2) バックドア調整公式 Σ_c (E[Y|X=1,c]-E[Y|X=0,c]) P(c)
p_c1 = (C == 1).mean()
p_c0 = (C == 0).mean()
eff_in_c1 = Y[(X == 1) & (C == 1)].mean() - Y[(X == 0) & (C == 1)].mean()
eff_in_c0 = Y[(X == 1) & (C == 0)].mean() - Y[(X == 0) & (C == 0)].mean()
backdoor = eff_in_c1 * p_c1 + eff_in_c0 * p_c0

# (3) 介入分布 P(Y|do(X)): Xの構造方程式を消し、全員のXを外から固定して Y を再生成
#     Cの分布は介入で変わらない(C は X の上流) → C←X 辺だけが切れる
Y_do1 = 1.0 + ATE_true * 1 + 3.0 * C + rng.normal(0.0, 1.0, size=n)
Y_do0 = 1.0 + ATE_true * 0 + 3.0 * C + rng.normal(0.0, 1.0, size=n)
do_effect = Y_do1.mean() - Y_do0.mean()

print(f"真の ATE                         : {ATE_true:.2f}")
print(f"(1) 観測 E[Y|X=1]-E[Y|X=0]       : {naive:.2f}  (交絡で過大)")
print(f"(2) バックドア調整 Σ_c ... P(c)   : {backdoor:.2f}")
print(f"(3) 介入 E[Y|do(1)]-E[Y|do(0)]    : {do_effect:.2f}")
print(f"   各層の効果: C=1で{eff_in_c1:.2f} / C=0で{eff_in_c0:.2f}  P(C=1)={p_c1:.2f}")
print(f"   処置群のC=1割合={C[X==1].mean():.2f} 対照群のC=1割合={C[X==0].mean():.2f}")

実行すると次のように印字されます。

真の ATE                         : 2.00
(1) 観測 E[Y|X=1]-E[Y|X=0]       : 3.79  (交絡で過大)
(2) バックドア調整 Σ_c ... P(c)   : 1.97
(3) 介入 E[Y|do(1)]-E[Y|do(0)]    : 2.01
   各層の効果: C=1で1.99 / C=0で1.96  P(C=1)=0.50
   処置群のC=1割合=0.80 対照群のC=1割合=0.20

出力の意味:素朴な条件付き差 3.793.79 は真値 2.02.0 を大きく上回ります。理由は最後の行に出ています——処置群は 80% が C=1C{=}1、対照群は 20% が C=1C{=}1 と構成が偏り、C=1C{=}1YY3.03.0 押し上げるので、群間の CC の差が処置効果に上乗せされるのです。これが観測分布の重み P(cx)P(c\mid x) が効いた結果です。一方、バックドア調整 1.971.97 と do介入 2.012.01 はどちらも真値 2.02.0 にほぼ一致します。バックドア調整は層内の効果(C=1C{=}11.991.99C=0C{=}01.961.96)を P(c)P(c) で重みづけし、do介入は XX を外から固定して CXC\to X を断つ——別ルートで同じ真値に到達しました。第2項と第3項が一致するのは、まさに前節で導いた等式 P(ydo(x))=cP(yx,c)P(c)P(y\mid do(x))=\sum_c P(y\mid x,c)P(c) が成り立っているからです。


5. 仮定の直観的意味:なぜ「矢印を切る」のか

do(X=x)do(X=x)XX への矢印を切るのは、「外からの操作は、XX を本来決めていた原因(CCUXU_X)を迂回する」からです。医師が重症度 CC を見て薬 XX を出す世界では CXC\to X がありますが、私たちがコインで XX を割り当てれば、CC はもう XX を決めません。ランダム化(なぜRCTが黄金律か)は、この dodo をデータ収集の段階で物理的に実装する行為にほかなりません。do演算子は「もし XX をランダム化していたら何が起きるか」を、観測データと SCM から計算するための代数なのです。

そして観測データだけから dodo を計算できる(識別できる)ための条件が、バックドアパスを塞ぐ調整集合の存在です。塞げない未観測交絡があれば P(ydo(x))P(y\mid do(x)) は観測分布で書けず、do計算は「識別不能」と正しく答えます。


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


関連ノート