Mímisbrunnr知恵の泉

← 因果推論 一覧

🎓 レベル:基礎 | 重要度:A(必須)

📎 前提:潜在結果モデルバックドア基準と識別 | 次に読む:なぜRCTが黄金律か | 数理:条件付き確率・独立性・全確率の定理(統計)

要点(BLUF)

1. 識別を支える4つの仮定

バックドア基準と識別 で、ATE が調整集合 ZZ

ATE  =  EZ ⁣[E[YX=1,Z]E[YX=0,Z]]\text{ATE} \;=\; E_{Z}\!\Big[\,E[Y\mid X{=}1, Z] - E[Y\mid X{=}0, Z]\,\Big]

と識別できることを見ました。この式の右辺が因果効果に等しくなるには、暗黙に次の4つが必要です。

仮定中身破れると
交換可能性 (exchangeability / ignorability){Y(1),Y(0)} ⁣ ⁣ ⁣XZ\{Y(1),Y(0)\}\perp\!\!\!\perp X \mid Z未観測交絡が残り、層内比較が因果でない
正値性 (positivity / overlap)0<P(X=1Z=z)<10 < P(X{=}1\mid Z{=}z) < 1(全層)比較相手のいない層が出て推定不能
一致性 (consistency)Y=Y(X)Y = Y(X)(観測値=割り当て処置の潜在結果)観測 YY と潜在結果 Y(x)Y(x) が結びつかない
SUTVA相互干渉なし+処置は単一バージョン個体の潜在結果 Yi(x)Y_i(x) が定義できない

2. 調整公式の導出:どこで各仮定を使うか

E[Y(1)]E[Y(1)](全員を処置した世界の平均)を、観測量だけで表してみます。各等号の上に使う仮定を書きます。

E[Y(1)]  =  EZ[E[Y(1)Z]]E[Y(1)] \;=\; E_{Z}\big[\,E[Y(1)\mid Z]\,\big]

これは全期待値の法則条件付き確率・独立性・全確率の定理)。ここで Y(1)Y(1) が個体ごとに定義できることが前提で、それを保証するのが SUTVA(他者の処置で自分の Y(1)Y(1) が変わらず、処置に複数版がない)です。次に

E[Y(1)Z]  =交換可能性  E[Y(1)X=1,Z]  =一致性  E[YX=1,Z]E[Y(1)\mid Z] \;\overset{\text{交換可能性}}{=}\; E[Y(1)\mid X{=}1, Z] \;\overset{\text{一致性}}{=}\; E[Y\mid X{=}1, Z]

1つ目の等号は 交換可能性ZZ を与えれば「誰が処置されたか」は潜在結果と無関係なので、X=1X=1 に絞っても Y(1)Y(1) の平均は変わりません。2つ目は 一致性X=1X=1 の人では観測値 YY がそのまま Y(1)Y(1) です。最後に、右辺 E[YX=1,Z=z]E[Y\mid X{=}1,Z{=}z]すべての層 zz で計算できるためには、各層に処置群が存在する必要があります——これが 正値性。同様に E[Y(0)]E[Y(0)] も表せて、差を取れば前節の ATE 公式になります。4つすべてが揃って初めて「観測量=因果効果」と書けるのです。

交換可能性は バックドア基準と識別 のバックドア基準で(観測交絡を正しく調整すれば)確保できます。残る正値性と SUTVA を、破ってみて何が起きるか見ます。

3. 正値性が破れると調整は推定不能になる

正値性は「どの層 Z=zZ=z にも、処置群と対照群が両方いる」こと。ある層に片方しかいなければ、その層の効果 E[YX=1,Z=z]E[YX=0,Z=z]E[Y|X{=}1,Z{=}z]-E[Y|X{=}0,Z{=}z]比較相手がいないので計算できません。真の効果を 2.02.0 と仕込み、正値性が成り立つ場合(A)と破れる場合(B)を比べます。

import numpy as np
import pandas as pd

# === 正値性(positivity):ある層に処置群(or 対照群)が居ないと調整が破綻する ===
rng = np.random.default_rng(3)
n = 50000

C = rng.integers(0, 5, size=n)        # 交絡 C:5 水準 {0,1,2,3,4}
ATE_true = 2.0


def simulate(prop_by_level):
    propensity = np.array(prop_by_level)[C]         # 各個体の傾向スコア e(C)
    X = rng.binomial(1, propensity)
    Y = ATE_true * X + 1.0 * C + rng.normal(0, 1, n)  # 真の効果 2.0
    return X, Y


def stratified_table(X, Y):
    rows = []
    for c in range(5):
        in_c = C == c
        n_treat = int((X[in_c] == 1).sum())
        n_ctrl = int((X[in_c] == 0).sum())
        if n_treat > 0 and n_ctrl > 0:
            effect = Y[in_c & (X == 1)].mean() - Y[in_c & (X == 0)].mean()
        else:
            effect = np.nan
        rows.append([c, n_treat, n_ctrl, round(effect, 3)])
    return pd.DataFrame(rows, columns=["C", "処置群n", "対照群n", "層内効果"])


# シナリオA:すべての層で 0<e<1(正値性 OK)
X_a, Y_a = simulate([0.2, 0.35, 0.5, 0.65, 0.8])
print("シナリオA(正値性 OK)")
tab_a = stratified_table(X_a, Y_a)
print(tab_a.to_string(index=False))
weights = [(C == c).mean() for c in range(5)]
print("層別調整 ATE =", round(np.average(tab_a["層内効果"], weights=weights), 3))

# シナリオB:両端で e=0 / e=1(正値性が破れる)
X_b, Y_b = simulate([0.0, 0.25, 0.5, 0.75, 1.0])
print("\nシナリオB(正値性が破れる)")
tab_b = stratified_table(X_b, Y_b)
print(tab_b.to_string(index=False))
print("→ C=0 は処置群0・C=4 は対照群0 で層内効果が NaN(推定不能)")

出力:

シナリオA(正値性 OK)
 C  処置群n  対照群n  層内効果
 0  2037  8065 2.011
 1  3389  6532 2.015
 2  5056  4929 2.021
 3  6575  3405 2.002
 4  7981  2031 1.978
層別調整 ATE = 2.005

シナリオB(正値性が破れる)
 C  処置群n  対照群n  層内効果
 0     0 10102   NaN
 1  2506  7415 1.980
 2  5013  4972 1.994
 3  7466  2514 2.009
 4 10012     0   NaN
→ C=0 は処置群0・C=4 は対照群0 で層内効果が NaN(推定不能)

出力の意味:シナリオ A は全層に両群がそろい、層別調整 ATE は 2.0052.02.005\approx2.0 で真値を当てます。シナリオ B は C=0C=0 で処置群が 0 人C=4C=4 で対照群が 0 人。これらの層では効果が NaN(推定不能) です。実務では正値性違反は「傾向スコアが 0011 に張り付く」「ある属性の人は全員が処置を受けている」という形で現れ、IPW では重み 1/e(z)1/e(z)発散します。対処は ① 共通サポート(両群が重なる範囲)に推定対象を限定する、② モデルで外挿する(ただし外挿は仮定であってデータではない)、のいずれかで、どちらも estimand が変わる/仮定が増えることを意識する必要があります(逆確率重み付けIPW)。

4. SUTVA が破れると素朴比較は政策効果を取り逃す

SUTVA(Stable Unit Treatment Value Assumption) は2つの中身を持ちます:(i) 相互干渉なし(自分の結果は他人の処置に依存しない)、(ii) 処置は単一バージョン(“処置する” の中身が一定)。(i) が破れる典型がワクチンの集団免疫です。自分が打たなくても周りが打てば感染が減る——他者の処置が自分の結果を変えます。すると「処置の効果」が一意に定まりません。

ここでは個人の直接効果を τ=1.0\tau=1.0、集団の処置割合によるスピルオーバーを spill=2.0\text{spill}=2.0 と仕込み、(i) 50%処置の RCT の素朴比較と、(ii) 全員処置 vs 誰も処置せずの政策効果を比べます。

import numpy as np

# === SUTVA(相互干渉なし)が破れると、RCTの素朴比較は政策効果を取り逃す ===
rng = np.random.default_rng(4)
n = 40000

# 真のモデル:個人の直接効果 tau=1.0、集団の処置割合によるスピルオーバー spill=2.0
tau, spill, base = 1.0, 2.0, 5.0

# (1) 50%処置のRCT(各人を独立にコイン投げ)
X = rng.binomial(1, 0.5, size=n)
frac_treated = X.mean()                 # 集団の処置割合(干渉=SUTVA違反の源)
Y = base + tau * X + spill * frac_treated + rng.normal(0, 1, n)
rct_naive = Y[X == 1].mean() - Y[X == 0].mean()

# (2) 政策効果:全員処置(frac=1) vs 誰も処置せず(frac=0) の真の対比
Y_all = base + tau * 1 + spill * 1.0
Y_none = base + tau * 0 + spill * 0.0
policy_effect = Y_all - Y_none

print(f"直接効果 tau (SUTVA下の個人効果) = {tau:.3f}")
print(f"RCTの素朴比較                    = {rct_naive:.3f}  ← 直接効果しか測れない")
print(f"政策効果 全員 vs 誰も (真値)      = {policy_effect:.3f}  ← 直接+スピルオーバー")
print(f"スピルオーバーによるギャップ      = {policy_effect - rct_naive:.3f}")

出力:

直接効果 tau (SUTVA下の個人効果) = 1.000
RCTの素朴比較                    = 0.991  ← 直接効果しか測れない
政策効果 全員 vs 誰も (真値)      = 3.000  ← 直接+スピルオーバー
スピルオーバーによるギャップ      = 2.009

出力の意味:RCT の素朴比較は 0.9910.991。処置群と対照群が同じ集団の処置割合を共有するので、スピルオーバー項が引き算で消え、直接効果 τ=1.0\tau=1.0 しか測れません。しかし「全員に配る」政策の真の効果は τ+spill=3.0\tau+\text{spill}=3.0。差の 2.02.02.009\approx2.009)がスピルオーバーで、SUTVA を素朴に仮定するとこの 2.02.0 を見落とします。干渉があるときは「処置の効果」が一意でなく、直接効果・スピルオーバー効果・全体効果を別々の estimand として定義し直す必要があります(部分干渉・クラスター設計など)。

5. 仮定の直観的意味と確認可能性

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

関連ノート