Mímisbrunnr知恵の泉

← 因果推論 一覧

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

📎 前提:相関と因果の違い | 次に読む:バックドア基準と識別 | 数理:条件付き確率・独立性・全確率の定理(統計)・構造方程式モデル・パス解析(統計)

要点(BLUF)

1. DAG と経路

DAG(Directed Acyclic Graph) は、変数をノード、直接の因果を矢印(有向辺)で表し、サイクルを持たないグラフです。ABA\to B は「AABB の直接の原因」を意味します。

経路(path) は、矢印の向きを無視してノードをたどった連なりです。経路には2種類あります。

経路が「相関を伝えるか(開)/伝えないか(閉)」を判定できれば、調整すべき変数が分かります。その判定規則が d 分離で、鍵は経路上の3部品です。

2. 3つの部品:鎖・分岐・合流点

どんな経路も、隣り合う3ノードの形が次の3つのいずれかです。中継点 ZZ を**条件づける(観測して揃える=統計的に固定する)**と、相関の伝わり方が部品ごとに変わります。

flowchart LR
  subgraph chain["鎖 chain"]
    X1["X"] --> Z1["Z"] --> Y1["Y"]
  end
  subgraph fork["分岐 fork"]
    Z2["Z"] --> X2["X"]
    Z2 --> Y2["Y"]
  end
  subgraph collider["合流点 collider"]
    X3["X"] --> Z3["Z"]
    Y3["Y"] --> Z3
  end

3. d分離の規則

経路上のある中継点を考えます。条件づける集合を ZZ とします。

経路は、上のいずれかで1か所でも閉じれば全体が閉。すべての経路が閉なら、XXYYZZ によって d分離(d-separated) され、次の条件付き独立が DAG から導かれます。

X ⁣ ⁣ ⁣dYZ        X ⁣ ⁣ ⁣YZX \perp\!\!\!\perp_d Y \mid Z \;\;\Longrightarrow\;\; X \perp\!\!\!\perp Y \mid Z

つまり「グラフ上の d 分離」→「分布上の条件付き独立」。これでデータを見る前に独立関係を予言できます。

4. 擬似データで d分離の予言を確かめる

3構造それぞれからデータを生成し、無条件の相関 r(X,Y)r(X,Y)ZZ を条件づけた偏相関 r(X,YZ)r(X,Y\mid Z) を測ります。偏相関は「ZZ で回帰した残差同士の相関」で計算します。

import numpy as np
import pandas as pd

# === 鎖・分岐・合流点のデータを作り、d分離の予言(独立/従属)を数値で確かめる ===
rng = np.random.default_rng(1)
n = 50000


def partial_corr(a, b, given):
    # given で線形回帰した残差同士の相関 = given を条件づけた偏相関
    design = np.column_stack([np.ones_like(given), given])
    coef_a, _, _, _ = np.linalg.lstsq(design, a, rcond=None)
    coef_b, _, _, _ = np.linalg.lstsq(design, b, rcond=None)
    resid_a = a - design @ coef_a
    resid_b = b - design @ coef_b
    return np.corrcoef(resid_a, resid_b)[0, 1]


# --- 鎖 X→Z→Y ---
X_chain = rng.normal(0, 1, n)
Z_chain = 1.0 * X_chain + rng.normal(0, 1, n)
Y_chain = 1.0 * Z_chain + rng.normal(0, 1, n)

# --- 分岐 X←Z→Y(Z は共通原因) ---
Z_fork = rng.normal(0, 1, n)
X_fork = 1.0 * Z_fork + rng.normal(0, 1, n)
Y_fork = 1.0 * Z_fork + rng.normal(0, 1, n)

# --- 合流点 X→Z←Y(Z は共通結果) ---
X_coll = rng.normal(0, 1, n)
Y_coll = rng.normal(0, 1, n)
Z_coll = 1.0 * X_coll + 1.0 * Y_coll + rng.normal(0, 1, n)

rows = [
    ["鎖 X→Z→Y",   np.corrcoef(X_chain, Y_chain)[0, 1], partial_corr(X_chain, Y_chain, Z_chain)],
    ["分岐 X←Z→Y", np.corrcoef(X_fork,  Y_fork)[0, 1],  partial_corr(X_fork,  Y_fork,  Z_fork)],
    ["合流 X→Z←Y", np.corrcoef(X_coll,  Y_coll)[0, 1],  partial_corr(X_coll,  Y_coll,  Z_coll)],
]
result = pd.DataFrame(rows, columns=["構造", "相関 r(X,Y)", "偏相関 r(X,Y | Z)"])
print(result.round(3).to_string(index=False))

出力:

      構造  相関 r(X,Y)  偏相関 r(X,Y | Z)
 鎖 X→Z→Y      0.580          -0.001
分岐 X←Z→Y      0.498          -0.000
合流 X→Z←Y     -0.001          -0.498

出力の意味:d分離の予言と完全に一致します。

なぜコライダーで相関が生まれるのか。Z=X+Y+noiseZ=X+Y+\text{noise} なので、ZZ をある値に固定すると「XX が大きければ YY は小さいはず(足して ZZ になるから)」という負の関係が強制されます。共通結果を観測して揃えると、無関係だった原因同士が結びつく——選択バイアスやサンプリングの落とし穴の正体です(第7章「感度分析と落とし穴」で深掘り)。

5. 仮定の直観的意味:なぜグラフが独立を予言できるのか

DAG は「各変数は親で決まる」という因果マルコフ条件を仮定します。これにより同時分布が

P(V1,,Vk)=iP ⁣(Vipa(Vi))P(V_1,\dots,V_k) = \prod_{i} P\!\left(V_i \mid \text{pa}(V_i)\right)

と親 pa(Vi)\text{pa}(V_i) への条件付き分布の積に分解されます。d分離は、この分解から導かれるすべての条件付き独立を、矢印の形だけから読み取るアルゴリズムです。だから「データを見る前に」独立関係が分かり、逆に「どの変数を条件づければ余計な経路を閉じられるか」が決まります。次ノートのバックドア基準は、この d分離を「交絡を消す調整集合の選び方」に翻訳したものです(バックドア基準と識別)。

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

関連ノート