Mímisbrunnr知恵の泉

← 因果推論 一覧

🎓 レベル:発展 | 重要度:A(必須)

📎 前提:識別の仮定 | 二重頑健推定AIPW

要点(BLUF)


1. 概念:識別は検証できない――だから感度分析

二重頑健推定AIPW までの調整法は、すべて条件付き交換可能性(観測した共変量 CC で層別すれば処置がランダムと見なせる)に依存していた。だがこの仮定はデータから検証できない。未観測の交絡 UU が処置 XX と結果 YY の両方を動かしていれば、どんなに丁寧に CC を調整しても推定値は偏る。

flowchart LR
    U["未観測交絡 U"] --> X["処置 X"]
    U --> Y["結果 Y"]
    C["観測共変量 C(調整済)"] --> X
    C --> Y
    X --> Y

感度分析は問いを反転させる。「未観測交絡はあるか/ないか」(答えられない)ではなく、**「観測された関連を未観測交絡だけで消し去るには、その交絡はどれほど強くなければならないか」**を計算する。求めた強さが「ありえないほど強い」なら結論は頑健、「いかにもありそう」なら結論は脆い。これを一目で伝える指標が E値だ。


2. 識別:E値の導出(バイアス係数 → 対称な最悪ケース)

二値の未観測交絡 UU を考える。Ding と VanderWeele(2016)は、交絡 UU が作れる最大のバイアス係数を次の2つの関連強度で抑えた。

このとき、観測リスク比 RRobs\mathrm{RR}_{\text{obs}} と真のリスク比 RRtrue\mathrm{RR}_{\text{true}} の比(=交絡が水増しした倍率)は、高々

B=RREURRUDRREU+RRUD1B = \frac{\mathrm{RR}_{EU}\,\mathrm{RR}_{UD}}{\mathrm{RR}_{EU} + \mathrm{RR}_{UD} - 1}

までしか大きくならない(これがシャープな上界である点が VanderWeele の貢献)。観測された関連を「実は効果ゼロ(RRtrue=1\mathrm{RR}_{\text{true}}=1)」へ完全に説明し去るには、BRRobsB \ge \mathrm{RR}_{\text{obs}} が必要になる。

ここで「最小の強さ」を求めるため、RREU\mathrm{RR}_{EU}RRUD\mathrm{RR}_{UD}大きいほうを最小化する。対称な点 RREU=RRUD=E\mathrm{RR}_{EU}=\mathrm{RR}_{UD}=E で最小になり、B=RRobsB=\mathrm{RR}_{\text{obs}} と置くと

E22E1=RRobsE22RRobsE+RRobs=0.\frac{E^2}{2E-1} = \mathrm{RR}_{\text{obs}} \quad\Longrightarrow\quad E^2 - 2\,\mathrm{RR}_{\text{obs}}\,E + \mathrm{RR}_{\text{obs}} = 0.

正の根を取って

E=RRobs+RRobs(RRobs1).E = \mathrm{RR}_{\text{obs}} + \sqrt{\mathrm{RR}_{\text{obs}}\,(\mathrm{RR}_{\text{obs}}-1)}.

これが E値だ。RRobs<1\mathrm{RR}_{\text{obs}}<1(予防的な効果)のときは 1/RRobs1/\mathrm{RR}_{\text{obs}} に直してから同じ式に入れる(E値は 11 をはさんで対称)。

❓ 確認:E値が大きいほど、結論は未観測交絡に対して頑健か脆いか? ―― 頑健。大きい E値は「説明し去るのにそれだけ強い交絡が要る=そう簡単には覆らない」を意味する。


3. 実証(1):E値は観測効果とともに増える

まず E値の関数を定義し、いくつかの観測 RR\mathrm{RR} に対する E値を表にする。

import numpy as np

# === 観測リスク比から E 値(E-value)を計算する ===
def e_value(rr):
    # RR<1 のときは逆数にして RR>=1 に揃える(E値は1未満/超で対称)
    if rr < 1.0:
        rr = 1.0 / rr
    return rr + np.sqrt(rr * (rr - 1.0))

for rr in [1.0, 1.25, 1.5, 2.0, 3.0, 5.0]:
    print(f"観測RR = {rr:5.2f}  ->  E値 = {e_value(rr):.3f}")

出力は次の通り。

観測RR =  1.00  ->  E値 = 1.000
観測RR =  1.25  ->  E値 = 1.809
観測RR =  1.50  ->  E値 = 2.366
観測RR =  2.00  ->  E値 = 3.414
観測RR =  3.00  ->  E値 = 5.449
観測RR =  5.00  ->  E値 = 9.472

出力の意味――観測 RR=1\mathrm{RR}=1(関連なし)なら E値も 11(交絡ゼロでも説明できる)。観測効果が大きくなるほど E値は急に増える。RR=2\mathrm{RR}=2 を消すには両方向に RR3.4\mathrm{RR}\approx 3.4 の交絡が、RR=5\mathrm{RR}=5 を消すには RR9.5\mathrm{RR}\approx 9.5 もの交絡が要る。強い関連ほど、それを偶然の交絡で片付けるのは難しいことが数値で見える。


4. 実証(2):真の効果ゼロのデータで E値を読む

ここが核心だ。処置 XX は結果 YY に一切影響しない(真の効果ゼロ)が、未観測交絡 UU が両方を動かすデータを作る。素朴な(UU を無視した)リスク比から E値を計算し、実際に仕込んだ交絡の強さと突き合わせる。

import numpy as np

# === 真の処置効果ゼロ。未観測交絡 U だけで見かけのリスク比(RR>1)が出る ===
rng = np.random.default_rng(7)
n = 200_000

U = rng.binomial(1, 0.5, size=n)                 # 未観測交絡(二値)
p_treat = np.where(U == 1, 0.8, 0.2)             # U=1 で処置されやすい
X = rng.binomial(1, p_treat)                     # 処置
p_out = np.where(U == 1, 0.40, 0.10)             # 結果は U に依存(X は不在)
Y = rng.binomial(1, p_out)                       # 真の処置効果は厳密にゼロ

def e_value(rr):
    if rr < 1.0:
        rr = 1.0 / rr
    return rr + np.sqrt(rr * (rr - 1.0))

# --- 素朴な(交絡を無視した)リスク比とその信頼区間 ---
a = Y[X == 1].sum(); n1 = (X == 1).sum()
c = Y[X == 0].sum(); n0 = (X == 0).sum()
p1, p0 = a / n1, c / n0
RR_obs = p1 / p0
se_log = np.sqrt(1/a - 1/n1 + 1/c - 1/n0)        # log RR の標準誤差(Katz)
lo = np.exp(np.log(RR_obs) - 1.96 * se_log)
hi = np.exp(np.log(RR_obs) + 1.96 * se_log)
print("真のRR = 1.000(処置は結果に無関係)")
print(f"観測RR = {RR_obs:.3f}  95%CI = [{lo:.3f}, {hi:.3f}]")
print(f"点推定の E値 = {e_value(RR_obs):.3f}")
print(f"CI下限の E値 = {e_value(lo):.3f}")

# --- 実際に仕込んだ交絡 U の強さ(リスク比スケール)---
RR_UD = Y[U == 1].mean() / Y[U == 0].mean()      # U→結果のリスク比
RR_EU = U[X == 1].mean() / U[X == 0].mean()      # 処置→U のリスク比
print(f"\n仕込んだ交絡の強さ: U→結果 RR = {RR_UD:.2f}, 処置→U RR = {RR_EU:.2f}")
B = (RR_EU * RR_UD) / (RR_EU + RR_UD - 1.0)
print(f"この交絡が作れる最大バイアス係数 B = {B:.3f} >= 観測RR {RR_obs:.3f}")

# --- もし U を観測できれば層別調整 → RR は 1 に戻る ---
num = den = 0.0
for u in [0, 1]:
    m = (U == u); w = m.mean()
    num += w * Y[m & (X == 1)].mean()
    den += w * Y[m & (X == 0)].mean()
print(f"U で層別調整した RR = {num / den:.3f}(真値 1.0 に戻る)")

出力は次の通り。

真のRR = 1.000(処置は結果に無関係)
観測RR = 2.127  95%CI = [2.092, 2.163]
点推定の E値 = 3.675
CI下限の E値 = 3.603

仕込んだ交絡の強さ: U→結果 RR = 3.99, 処置→U RR = 3.98
この交絡が作れる最大バイアス係数 B = 2.280 >= 観測RR 2.127
U で層別調整した RR = 1.003(真値 1.0 に戻る)

出力の意味――いくつもの数字が一本につながる。

E値は「未観測交絡があるか」には答えない。だが「もしあるなら、これだけ強くなければ説明できない」という具体的な基準を与える。あとは分野知識で「RR=3.7\mathrm{RR}=3.7 ほど強い未測定要因はありそうか」を問えばよい。本例では現にそれが存在したので、結論は脆かった。


5. 図:E値曲線

E値が観測効果とともにどう増えるかを描く。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib

# === E値曲線: 観測効果が大きいほど、説明し去るのに強い交絡が要る ===
def e_value(rr):
    return rr + np.sqrt(rr * (rr - 1.0))

rr_grid = np.linspace(1.0, 5.0, 200)
ev = e_value(rr_grid)

fig, ax = plt.subplots(figsize=(7, 4.5))
ax.plot(rr_grid, ev, lw=2.2, color="#1f77b4", label="E値")
ax.plot(rr_grid, rr_grid, ls="--", color="gray", label="参照線 y=x")
for r in [1.5, 2.0, 3.0]:
    ax.plot([r], [e_value(r)], "o", color="crimson")
    ax.annotate(f"RR={r}\nE値={e_value(r):.2f}", (r, e_value(r)),
                textcoords="offset points", xytext=(8, -2), fontsize=9)
ax.set_xlabel("観測リスク比 RR")
ax.set_ylabel("E値(必要な未観測交絡の強さ)")
ax.set_title("観測効果が大きいほど、説明し去るのに強い交絡が要る")
ax.legend()
plt.tight_layout()
plt.show()

E値曲線は常に参照線 y=xy=x より上にある(RR(RR1)>0\sqrt{\mathrm{RR}(\mathrm{RR}-1)}>0)。つまり必要な交絡の強さは、観測されたリスク比そのものより必ず大きい。これが「弱い関連は交絡で消えやすく、強い関連は消えにくい」ことの幾何学的な表れだ。


6. 使いどころ


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


関連ノート