인과 추론 프레임워크 총정리

Potential Outcomes, DAG, Matching, IV — 관찰 데이터에서 인과를 추론하는 무기들

인과 추론의 두 대 프레임워크(Rubin Causal Model과 Pearl의 Structural Causal Model), 핵심 도구(Matching, Propensity Score, Instrumental Variables, Mediation Analysis), 그리고 종단 데이터 맥락의 인과 추론(G-methods, Marginal Structural Model)을 Python과 R 코드로 정리한다.

Statistics
Causal Inference
Study Design
저자

Kwangmin Kim

공개

2026년 03월 08일

1 1. 왜 인과 추론인가

1.1 Correlation \(\neq\) Causation

통계 분석의 궁극적 목표 중 하나는 “X가 Y를 일으키는가?”에 답하는 것이다. 그러나 관찰 데이터에서 발견한 상관관계는 인과가 아닐 수 있다.

아이스크림 판매량과 익사 사고 수는 강한 양의 상관을 보인다. 아이스크림이 익사를 유발하는가? 아니다 — 공통 원인(confounder)인 기온이 둘 다 올린다.

1.2 Simpson’s Paradox

치료 성공 치료 실패 성공률
전체: 약물 A 200/350 150/350 57%
전체: 약물 B 180/350 170/350 51%

약물 A가 더 좋아 보인다. 그런데 중증도별로 나누면:

중증도 약물 A 성공률 약물 B 성공률
경증 80/100 = 80% 230/270 = 85%
중증 120/250 = 48% 50/80 = 63%

모든 하위 그룹에서 B가 우월하다. A가 전체적으로 좋아 보인 이유는 A가 중증 환자에게 더 많이 처방되었기 때문이다. 이것이 Simpson’s Paradox이며, 교란 변수(중증도)를 통제하지 않으면 잘못된 결론에 도달한다.

1.3 교란(Confounding)과 선택 편향(Selection Bias)

위협 정의 예시
Confounding 처리와 결과에 모두 영향을 주는 제3변수 교육 수준 → (수입, 건강)
Selection Bias 처리 할당이 결과와 관련된 특성에 의존 건강한 사람이 자발적으로 운동 프로그램 참여
Reverse Causation Y → X 방향이 실제 인과 부유한 나라가 민주주의? 민주주의가 부유?

1.4 RCT는 Gold Standard이지만…

Randomized Controlled Trial(RCT)은 무작위 배정으로 confounding을 제거하는 최강의 설계다 (32편 참조). 그러나:

  • 윤리적 불가: 흡연의 폐암 효과를 실험할 수 없다
  • 비용/시간: 대규모 장기 RCT는 수십억 원
  • 외적 타당도: 실험실 조건 \(\neq\) 현실
  • 이미 수집된 관찰 데이터: EHR, 로그 데이터, 행정 데이터

따라서 관찰 데이터에서 인과를 추론하는 체계적 프레임워크가 필수다.


2 2. Rubin Causal Model (Potential Outcomes)

2.1 잠재 결과 프레임워크

Donald Rubin이 체계화한 Potential Outcomes Framework (Neyman-Rubin Causal Model)은 인과를 반사실(counterfactual)로 정의한다.

개체 \(i\)에 대해:

  • \(T_i \in \{0, 1\}\): 처리 여부 (treatment indicator)
  • \(Y_i(1)\): 처리를 받았을 때의 잠재 결과
  • \(Y_i(0)\): 처리를 받지 않았을 때의 잠재 결과

개체 수준 인과 효과 (Individual Causal Effect):

\[ \tau_i = Y_i(1) - Y_i(0) \]

2.2 인과 추론의 근본 문제 (Fundamental Problem)

각 개체는 \(Y_i(1)\) 또는 \(Y_i(0)\)하나만 관찰할 수 있다.

\[ Y_i^{obs} = T_i \cdot Y_i(1) + (1 - T_i) \cdot Y_i(0) \]

관찰되지 않는 잠재 결과를 counterfactual이라 한다. \(\tau_i\)는 직접 계산할 수 없으므로 집단 수준의 인과 효과를 추정한다.

2.3 인과 효과의 종류

추정량 정의 해석
ATE (Average Treatment Effect) \(E[Y(1) - Y(0)]\) 전체 집단에서 평균적 처리 효과
ATT (ATE on the Treated) \(E[Y(1) - Y(0) \mid T=1]\) 실제 처리받은 집단의 평균 효과
ATC (ATE on the Control) \(E[Y(1) - Y(0) \mid T=0]\) 비처리 집단이 처리받았다면?
CATE (Conditional ATE) \(E[Y(1) - Y(0) \mid X=x]\) 특정 특성을 가진 하위 집단의 효과
SATE \(\frac{1}{n}\sum_i [Y_i(1) - Y_i(0)]\) 표본 내 평균 효과
PATE \(E_{pop}[Y(1) - Y(0)]\) 모집단 평균 효과

2.4 식별 가정 (Identification Assumptions)

ATE를 관찰 데이터에서 식별하려면 다음 가정이 필요하다:

1. 무시가능성 (Ignorability / Unconfoundedness)

\[ \{Y(1), Y(0)\} \perp\!\!\!\perp T \mid X \]

공변량 \(X\)를 조건부로 하면, 처리 할당이 잠재 결과와 독립이다. 즉, 관측된 \(X\)로 모든 교란을 설명할 수 있다.

2. 양의 확률 (Positivity / Overlap)

\[ 0 < P(T=1 \mid X=x) < 1 \quad \forall x \]

모든 공변량 수준에서 처리/비처리 모두 가능해야 한다.

3. SUTVA (Stable Unit Treatment Value Assumption)

  • No interference: \(i\)의 결과가 \(j\)의 처리에 영향받지 않음
  • No hidden variations: 처리의 버전이 하나임

SUTVA 하에서, 무시가능성과 양의 확률이 성립하면:

\[ \text{ATE} = E_X \big[ E[Y \mid T=1, X] - E[Y \mid T=0, X] \big] \]


3 3. Pearl의 Structural Causal Model (SCM)

3.1 DAG (Directed Acyclic Graph)

Judea Pearl이 제안한 Structural Causal Model은 변수 간 인과 구조를 DAG로 표현한다.

    U
   / \
  v   v
  T --> Y
  ^   /
  |  /
  X
  • Node: 변수
  • Edge (→): 직접 인과 관계
  • Acyclic: 순환 없음

3.2 구조 방정식 (Structural Equations)

SCM은 각 변수를 부모 변수와 외생 변수(noise)의 함수로 표현한다:

\[ \begin{aligned} X &= f_X(U_X) \\ T &= f_T(X, U_T) \\ Y &= f_Y(T, X, U_Y) \end{aligned} \]

여기서 \(U_X, U_T, U_Y\)는 서로 독립인 외생 변수(exogenous noise)다.

3.3 do-calculus와 개입

Pearl의 핵심 통찰: 관찰(seeing)개입(doing)은 다르다.

  • 관찰: \(P(Y \mid T=1)\) — “T=1인 사람의 Y”
  • 개입: \(P(Y \mid do(T=1))\) — “T를 1로 강제 설정했을 때 Y”

\(do(T=t)\)는 DAG에서 \(T\)로 들어오는 모든 화살표를 절단하고 \(T=t\)로 고정한다. 이를 mutilated graph라 한다.

\[ \text{ATE} = E[Y \mid do(T=1)] - E[Y \mid do(T=0)] \]

3.4 d-Separation

DAG에서 두 변수 사이의 조건부 독립을 판별하는 규칙이다.

세 가지 기본 구조:

구조 패턴 \(X\) 조건부 경로 차단?
Chain (매개) \(A \to B \to C\) \(B\)를 조건부 차단
Fork (교란) \(A \leftarrow B \to C\) \(B\)를 조건부 차단
Collider (충돌) \(A \to B \leftarrow C\) 조건부 안 함 이미 차단
\(B\)를 조건부 열림!

Collider에서 조건부를 걸면 경로가 열리는 것이 핵심이다. 이것이 collider bias (selection bias)의 원인이다.

Collider Bias의 직관적 이해:

Collider(\(B\))는 두 원인(\(A\), \(C\))이 동시에 영향을 주는 결과 변수다. \(B\)를 조건부로 고정하면, 본래 무관한 \(A\)\(C\) 사이에 가짜 상관이 생긴다.

IT 예시: 채용 편향

  코딩 실력(A) ──→ 합격(B) ←── 의사소통 능력(C)

  A와 C는 원래 독립이다 (코딩과 소통은 무관).
  그런데 "합격자(B=1)"만 분석하면?
  → 합격자 중에서는 코딩이 약한 사람은 소통이 뛰어난 덕에 합격한 것
  → 코딩 ↑ 이면 소통 ↓ 인 것처럼 보이는 음의 상관이 생김
  → 이것은 실제 인과가 아니라 collider bias

IT A/B 테스트에서의 collider bias:

  처치(T) ──→ 체류시간(M) ←── 관심도(U)

  "체류시간이 긴 사용자"만 분석하면?
  → M을 조건부하면 T와 U 사이에 가짜 경로가 열림
  → 처치의 효과가 왜곡됨
  → 해결: M(체류시간)으로 서브그룹 분석하지 말 것!

일반 규칙: 결과 변수(또는 결과의 후손)를 조건부/통제하면 collider bias 발생
           → 분석 시 "어떤 변수를 통제해야 하는가"는 DAG를 그려야만 판단 가능

3.5 Backdoor Criterion

변수 집합 \(Z\)\((T, Y)\)에 대해 backdoor criterion을 만족하면:

  1. \(Z\)의 어떤 변수도 \(T\)의 후손(descendant)이 아니다
  2. \(Z\)\(T\)\(Y\) 사이의 모든 backdoor path를 차단한다

이때:

\[ P(Y \mid do(T=t)) = \sum_z P(Y \mid T=t, Z=z) \cdot P(Z=z) \]

이를 backdoor adjustment (또는 adjustment formula)라 한다.

Backdoor의 직관:

“Backdoor path”란 처치(\(T\))에서 결과(\(Y\))로 가는 경로 중, \(T\)들어오는 화살표를 타고 역방향으로 거슬러 올라가는 경로다. 이 경로가 열려 있으면 교란이 발생한다. Backdoor criterion을 만족하는 \(Z\)를 조건부로 하면 이 뒷문 경로를 차단하여 \(T \to Y\) 직접 효과만 남긴다.

예: IT 서비스에서 새 기능(T)이 구매(Y)에 미치는 효과

     사용자 세그먼트(Z)
        ↙        ↘
  새 기능 사용(T) → 구매(Y)

  파워 유저(Z=power)는 새 기능을 더 많이 쓰고(T↑),
  동시에 원래 구매도 더 많이 한다(Y↑).
  Z를 통제하지 않으면 T → Y 효과에 Z의 영향이 섞임.

  Backdoor path: T ← Z → Y
  Z를 조건부로 하면 이 경로가 차단됨
  → 세그먼트별로 T의 Y 효과를 추정한 뒤 가중 평균

3.6 Frontdoor Criterion

\(T \to M \to Y\) 구조에서 \(T \leftarrow U \to Y\)의 미관측 교란이 있을 때, 매개변수 \(M\)을 통한 식별:

\[ P(Y \mid do(T=t)) = \sum_m P(M=m \mid T=t) \sum_{t'} P(Y \mid M=m, T=t') P(T=t') \]

Frontdoor criterion은 미관측 교란이 있어도 인과 효과를 식별할 수 있는 강력한 도구다. 단, \(T \to M\) 경로에 교란이 없어야 한다.

Frontdoor Criterion의 직관:

Backdoor criterion은 “교란 변수를 직접 관측하여 통제”하는 방식이다. 하지만 교란 변수가 미관측이면 backdoor는 쓸 수 없다. Frontdoor는 이 상황에서 매개변수(\(M\))를 경유하여 우회적으로 인과 효과를 식별한다.

상황: 흡연(T) → 타르 축적(M) → 폐암(Y)
      흡연(T) ←── 유전적 성향(U) ──→ 폐암(Y)
      U는 미관측이므로 backdoor 조정 불가

Frontdoor 전략 (2단계):
  1단계: T → M 효과 추정
    흡연 → 타르 축적은 U와 무관하게 추정 가능
    (유전적 성향은 타르 축적량에 직접 영향 없음)

  2단계: M → Y 효과 추정
    타르 → 폐암 효과는 T를 조건부로 하면 U의 교란이 차단됨
    (T를 통제하면 T ← U → Y 경로가 차단)

  결합: T → M → Y 전체 효과 = (T→M 효과) × (M→Y 효과)

IT 적용 가능 시나리오:

광고 노출(T) → 앱 설치(M) → 구매(Y)
광고 노출(T) ←── 사용자 관심도(U) ──→ 구매(Y)

U(관심도)는 직접 측정이 어렵지만,
"광고 → 앱 설치" 경로와 "앱 설치 → 구매" 경로를
각각 식별할 수 있다면 frontdoor로 광고의 구매 효과를 추정 가능

단, 실무적 제약: "M에 영향을 주는 것이 오직 T뿐"이라는
가정이 성립하기 어려운 경우가 많아, 적용 범위는 제한적

4 4. RCM vs SCM 비교

차원 Rubin Causal Model (RCM) Pearl’s Structural Causal Model (SCM)
핵심 개념 잠재 결과 \(Y(1), Y(0)\) 구조 방정식 + DAG
인과 정의 \(Y_i(1) - Y_i(0)\) \(P(Y \mid do(T))\)
가정 표현 수학적 조건 (ignorability) 그래프 (DAG)
장점 추정량 정의가 명확, 통계적 가정을 시각적으로 인코딩, 직관적
약점 가정의 타당성 판단이 어려움 비모수적 식별에 머무를 수 있음
주 사용 분야 역학, 경제학, 사회과학 CS, AI, 역학
Mediation 최근에 통합 (VanderWeele) 자연스럽게 표현
Time-varying Robins의 G-methods로 확장 동적 DAG로 확장

실무적 결론: 두 프레임워크는 상호 보완적이다.

  • DAG로 가정을 명시하고, 어떤 변수를 통제해야 하는지 판단
  • Potential outcomes로 추정량을 정의하고, 통계적 추론 수행

5 5. Matching & Propensity Score

5.1 매칭의 직관

RCT에서 처리/대조군은 모든 공변량에서 균형(balance)을 이룬다. 관찰 데이터에서 이 균형을 사후적으로 만드는 것이 매칭이다.

5.2 Exact Matching

공변량 \(X\)의 값이 정확히 같은 처리군-대조군 쌍을 매칭한다.

  • 장점: 완벽한 균형
  • 단점: 차원의 저주 — 공변량이 많으면 매칭 불가능한 개체 증가

5.3 Coarsened Exact Matching (CEM)

공변량을 구간(bin)으로 나눈 뒤 exact matching. 차원의 저주를 완화한다.

5.4 Nearest Neighbor Matching

거리(Mahalanobis, Euclidean)가 가장 가까운 쌍을 매칭한다.

\[ d(i,j) = (X_i - X_j)^T \Sigma^{-1} (X_i - X_j) \quad \text{(Mahalanobis)} \]

5.5 Propensity Score: 정의

Propensity Score는 공변량이 주어졌을 때 처리를 받을 확률이다:

\[ e(X) = P(T = 1 \mid X) \]

Rosenbaum & Rubin (1983)의 핵심 정리:

\(\{Y(1), Y(0)\} \perp\!\!\!\perp T \mid X\) 이면 \(\{Y(1), Y(0)\} \perp\!\!\!\perp T \mid e(X)\)

즉, 다차원 \(X\) 대신 1차원 스칼라 \(e(X)\)로 조건부를 대체할 수 있다.

5.6 Propensity Score 추정

가장 일반적인 방법은 로지스틱 회귀:

\[ \log \frac{e(X)}{1 - e(X)} = \beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p \]

GBM, Random Forest 등 ML 방법도 사용 가능하다.

5.7 PSM (Propensity Score Matching)

추정된 \(\hat{e}(X)\)를 기준으로 nearest neighbor matching을 수행한다.

\[ \widehat{\text{ATT}} = \frac{1}{n_1} \sum_{i: T_i=1} \left[ Y_i - Y_{j(i)} \right] \]

여기서 \(j(i)\)\(i\)에 매칭된 대조군이다.

5.8 IPTW (Inverse Probability of Treatment Weighting)

매칭 대신, 각 개체에 가중치를 부여하여 pseudo-population을 만든다:

\[ w_i = \frac{T_i}{\hat{e}(X_i)} + \frac{1 - T_i}{1 - \hat{e}(X_i)} \]

ATE 추정:

\[ \widehat{\text{ATE}}_{IPTW} = \frac{\sum_i T_i Y_i / \hat{e}(X_i)}{\sum_i T_i / \hat{e}(X_i)} - \frac{\sum_i (1-T_i) Y_i / (1 - \hat{e}(X_i))}{\sum_i (1-T_i) / (1 - \hat{e}(X_i))} \]

5.9 Stratification

\(\hat{e}(X)\)를 5~10개 구간으로 나누고, 각 구간 내에서 처리 효과를 추정한 뒤 가중 평균한다.

5.10 Doubly Robust Estimator

결과 모델(outcome regression)과 처리 모델(propensity score)을 동시에 사용한다:

\[ \widehat{\text{ATE}}_{DR} = \frac{1}{n} \sum_{i=1}^{n} \left[ \hat{\mu}_1(X_i) - \hat{\mu}_0(X_i) + \frac{T_i (Y_i - \hat{\mu}_1(X_i))}{\hat{e}(X_i)} - \frac{(1-T_i)(Y_i - \hat{\mu}_0(X_i))}{1-\hat{e}(X_i)} \right] \]

여기서 \(\hat{\mu}_t(X) = \hat{E}[Y \mid T=t, X]\). 두 모델 중 하나만 맞아도 ATE가 일치 추정량이 된다.

5.11 Balance Diagnostics

매칭/가중 후 공변량 균형을 반드시 확인해야 한다:

  • Standardized Mean Difference (SMD): \(|SMD| < 0.1\) 이면 양호
  • Variance Ratio: 0.5 ~ 2.0 범위
  • Love Plot: SMD를 시각화

\[ \text{SMD} = \frac{\bar{X}_{T=1} - \bar{X}_{T=0}}{\sqrt{(s_{T=1}^2 + s_{T=0}^2)/2}} \]


6 6. Instrumental Variables (IV)

6.1 왜 IV가 필요한가

Ignorability가 성립하지 않는 경우 — 즉, 미관측 교란(unmeasured confounding)이 있을 때 — matching이나 propensity score는 편향된다. 이때 Instrumental Variable이 대안이다.

6.2 IV의 정의

변수 \(Z\)\(T\)에 대한 도구변수이려면 세 가지 조건을 만족해야 한다:

  1. Relevance: \(Z\)\(T\)가 상관 — \(\text{Cov}(Z, T) \neq 0\)
  2. Exclusion Restriction: \(Z\)\(T\)를 통해서만 \(Y\)에 영향 — \(Z \to T \to Y\) (직접 경로 \(Z \to Y\) 없음)
  3. Independence: \(Z\)와 미관측 교란 \(U\)가 독립 — \(Z \perp\!\!\!\perp U\)

DAG로 표현하면:

Z ----> T ----> Y
          ^    ^
          |    |
          U ---+

\(Z\)\(T\)에 영향을 주지만, \(Y\)에는 \(T\)를 통해서만 영향을 준다.

6.3 2SLS (Two-Stage Least Squares)

1단계: \(T\)\(Z\)에 회귀하여 예측값 \(\hat{T}\) 계산

\[ T_i = \alpha_0 + \alpha_1 Z_i + \epsilon_i \quad \Rightarrow \quad \hat{T}_i = \hat{\alpha}_0 + \hat{\alpha}_1 Z_i \]

2단계: \(Y\)\(\hat{T}\)에 회귀

\[ Y_i = \beta_0 + \beta_1 \hat{T}_i + \eta_i \]

\(\hat{\beta}_1\)이 인과 효과의 일치 추정량이다.

6.4 간단한 IV 추정량 (Wald Estimator)

\(Z\)가 이진변수일 때:

\[ \hat{\beta}_{IV} = \frac{\bar{Y}_{Z=1} - \bar{Y}_{Z=0}}{\bar{T}_{Z=1} - \bar{T}_{Z=0}} = \frac{\text{Reduced Form}}{\text{First Stage}} \]

6.5 Weak Instruments Problem

\(Z\)\(T\)의 상관이 약하면 (\(F\)-statistic < 10), 2SLS 추정량은 심한 편향과 큰 분산을 가진다. 1단계 F-통계량 > 10이 전통적 기준이다 (Stock & Yogo).

6.6 LATE 해석

IV가 추정하는 것은 ATE가 아니라 LATE (Local Average Treatment Effect):

\[ \text{LATE} = E[Y(1) - Y(0) \mid \text{Compliers}] \]

Compliers: 도구변수 \(Z\)의 값에 따라 처리 여부가 바뀌는 하위 집단. Always-takers나 Never-takers의 효과는 식별되지 않는다.

6.7 예시: 병원까지 거리

  • \(Y\): 건강 결과
  • \(T\): 특정 시술 수행 여부
  • \(U\): 환자 중증도 (미관측)
  • \(Z\): 병원까지 거리

병원이 가까울수록 시술을 받을 가능성이 높지만, 거리 자체는 건강에 직접 영향을 주지 않는다고 가정.


7 7. Mediation Analysis

7.1 직접 효과 vs 간접 효과

\(T\)\(Y\)에 미치는 효과를 분해:

\[ T \xrightarrow{\text{direct}} Y \quad \text{vs} \quad T \to M \to Y \quad \text{(indirect, through mediator M)} \]

예시: AI Agent의 새로운 UX 디자인(\(T\))이 사용자 만족도(\(Y\))에 미치는 효과.

  • 직접 효과: UX 디자인 자체의 만족도 변화
  • 간접 효과: UX → 체류 시간(\(M\)) 증가 → 만족도 향상

7.2 Baron-Kenny Approach (전통적)

세 개의 회귀를 순차적으로 수행:

  1. \(Y = c_0 + cT + \epsilon_1\) — Total effect
  2. \(M = a_0 + aT + \epsilon_2\)\(T \to M\) 경로
  3. \(Y = c_0' + c'T + bM + \epsilon_3\) — Direct effect (\(c'\))

간접 효과 = \(a \times b\) 또는 \(c - c'\).

한계: 선형성 가정, 상호작용 무시, M에 대한 교란 미처리.

7.3 인과적 매개분석 (Causal Mediation)

Pearl의 프레임워크에서 정의하는 Natural Direct Effect (NDE)Natural Indirect Effect (NIE):

\[ \text{NDE} = E[Y(1, M(0)) - Y(0, M(0))] \]

\[ \text{NIE} = E[Y(1, M(1)) - Y(1, M(0))] \]

여기서 \(Y(t, m)\)\(T=t, M=m\)일 때의 잠재 결과이고, \(M(t)\)\(T=t\)일 때의 잠재 매개변수다.

Total Effect = NDE + NIE:

\[ E[Y(1) - Y(0)] = \underbrace{E[Y(1,M(1)) - Y(1,M(0))]}_{\text{NIE}} + \underbrace{E[Y(1,M(0)) - Y(0,M(0))]}_{\text{NDE}} \]

7.4 식별 조건

NDE와 NIE를 식별하려면:

  1. \(T \to Y\) 교란 없음 (given \(X\))
  2. \(M \to Y\) 교란 없음 (given \(T, X\))
  3. \(T \to M\) 교란 없음 (given \(X\))
  4. \(T\)에 의해 영향받는 \(M \to Y\) 교란이 없음 (sequential ignorability)

조건 4가 가장 강하고 검증이 어렵다.

7.5 매개분석이 의미 있는 경우

  • 메커니즘 이해: 처리가 “어떻게” 효과를 내는지
  • 개입 대상 식별: 간접 효과가 크면 매개변수에 개입
  • 설계 최적화: AI Agent에서 어떤 경로가 engagement를 높이는지

8 8. 종단 데이터의 인과 추론

8.1 시간 변동 교란 (Time-Varying Confounding)

종단 데이터에서는 교란 변수 \(L_t\)가 시간에 따라 변하고, 이전 처리 \(T_{t-1}\)에 의해 영향받을 수 있다:

L_0 --> T_0 --> L_1 --> T_1 --> Y
 \       \      ^  \     ^
  \       +-----+   +----+
   +----------------------------> Y

\(L_1\)\(T_0\)의 결과이면서 \(T_1\)의 교란이다. 이런 변수를 time-varying confounder affected by prior treatment라 한다.

8.2 왜 표준 회귀가 실패하는가

  • \(L_1\)통제하면: \(T_0 \to L_1 \to Y\) 경로가 차단되어 \(T_0\)의 효과 일부가 손실 (overcontrol)
  • \(L_1\)통제하지 않으면: \(L_1 \to T_1\), \(L_1 \to Y\) 교란이 남아 편향

\(L_1\)매개변수이자 교란변수이므로, 통제해도 안 하고 안 해도 안 된다. 이것이 time-varying confounding의 딜레마이며, 표준 회귀로는 해결 불가능하다.

IT 구체 예시: 프로모션의 누적 효과

상황: 매월 프로모션(T)을 할지 결정, 6개월 후 고객 LTV(Y) 추정

  L₀(1월 engagement) → T₀(1월 프로모) → L₁(2월 engagement) → T₁(2월 프로모) → Y

문제:
  L₁(2월 engagement)은 T₀(1월 프로모션)의 결과이면서
  동시에 T₁(2월 프로모션 여부)를 결정하는 교란 변수

  - L₁을 통제하면: T₀ → L₁ → Y 경로가 차단됨
    → 1월 프로모션이 engagement를 높여 LTV를 올린 효과가 사라짐
    → T₀의 효과 과소 추정 (overcontrol bias)

  - L₁을 통제 안 하면: L₁ → T₁ + L₁ → Y 교란이 남음
    → engagement가 높은 사용자에게 더 프로모션을 줬는데,
       engagement 자체가 LTV를 높이는 것인지 프로모가 높이는 것인지 구분 불가
    → T₁의 효과 과대 추정

  → 어느 쪽이든 편향! 표준 회귀(OLS/로지스틱)로는 해결 불가능
  → G-methods(아래)가 필요한 이유

8.3 G-Methods: 해결책

James Robins가 제안한 G-methods는 이 딜레마를 해결한다. 핵심 아이디어는 “통제할지 말지”의 이분법을 넘어, 가상의 개입 세계(interventional world)를 시뮬레이션하거나 가중치로 만들어내는 것이다.

세 가지 G-methods의 직관:

1. G-Computation: "만약 모든 사용자에게 매월 프로모션을 줬다면?"이라는
   반사실 시나리오를 데이터의 조건부 분포로부터 시뮬레이션한다.
   → 시간 순서대로 L₁, L₂, ... 의 분포를 순차 생성하며
     각 시점에서 처치를 강제 고정한 궤적을 만들어 비교

2. IPTW (역확률 가중): 각 사용자가 "실제로 받은 처치 이력"의 확률의
   역수를 가중치로 부여하여, 마치 무작위 배정된 것처럼 보이는
   가상 모집단(pseudo-population)을 생성한다.
   → 가중 후에는 L₁이 더 이상 T₁의 교란이 아니게 됨

3. G-Estimation: 각 시점에서 "이 시점의 처치가 없었다면 결과가
   얼마나 달랐을까"(blip function)를 역순으로 추정한다.
   → 마지막 시점부터 거슬러 올라가며 각 시점의 인과 효과를 분리

8.3.1 G-Computation (G-formula)

순차적으로 조건부 기대값을 계산:

\[ E[Y(\bar{t})] = \sum_{\bar{l}} E[Y \mid \bar{T}=\bar{t}, \bar{L}=\bar{l}] \prod_{k=0}^{K} P(L_k \mid \bar{T}_{k-1}=\bar{t}_{k-1}, \bar{L}_{k-1}=\bar{l}_{k-1}) \]

여기서 \(\bar{t} = (t_0, t_1, \ldots, t_K)\)는 처리 이력(treatment history)이다.

  • 장점: 모수적 모델로 구현 가능
  • 단점: 모든 모델을 올바르게 지정해야 함 (모델 의존적)

8.3.2 IPTW for Time-Varying Treatments

각 시점에서 처리 확률의 역수를 가중치로 사용:

\[ w_i = \prod_{t=0}^{K} \frac{1}{P(T_{it} \mid \bar{T}_{i,t-1}, \bar{L}_{it})} \]

이 가중치는 처리 할당과 시간 변동 교란의 연결을 끊어 pseudo-population을 생성한다.

8.3.3 G-Estimation

Structural Nested Mean Model (SNMM)을 통해 각 시점에서의 blip function (해당 시점 처리의 인과 효과)을 추정한다:

\[ E[Y(\bar{t}_{k}, \mathbf{0}_{k+1:K}) - Y(\bar{t}_{k-1}, \mathbf{0}_{k:K}) \mid \bar{T}_k = \bar{t}_k, \bar{L}_k] = \gamma(t_k, \bar{h}_k; \psi) \]

8.4 Marginal Structural Model (MSM)

IPTW를 사용하여 가중치가 부여된 결과 모델을 적합:

\[ E[Y(\bar{t})] = \beta_0 + \beta_1 \cdot \text{cum}(\bar{t}) \]

여기서 \(\text{cum}(\bar{t}) = \sum_{k=0}^K t_k\)는 누적 처리량이다.

8.5 Stabilized Weights

표준 IPTW 가중치는 분산이 매우 클 수 있다. Stabilized weights를 사용하면:

\[ sw_i = \prod_{t=0}^{K} \frac{P(T_{it} \mid \bar{T}_{i,t-1})}{P(T_{it} \mid \bar{T}_{i,t-1}, \bar{L}_{it})} \]

분자에 이전 처리 이력만의 조건부 확률을 넣어 가중치의 분산을 줄인다.

가중치 진단:

  • 평균이 1에 가까워야 함
  • 극단값(> 10~20) 확인 → truncation 또는 trimming 적용
  • 가중 후 공변량 균형 확인

9 9. Python 실무 예시

9.1 시나리오: AI Agent A/B 테스트의 교란

AI Agent의 새로운 추천 알고리즘(\(T\))이 사용자 engagement(\(Y\))에 미치는 효과를 추정하고 싶다. 그러나 파워 유저일수록 새 기능을 자발적으로 활성화하므로, 사용자 활동 수준(\(X\))이 교란 변수다.

9.2 DoWhy를 활용한 인과 추론

import numpy as np
import pandas as pd
import dowhy
from dowhy import CausalModel

# --- 시뮬레이션 데이터 생성 ---
np.random.seed(42)
n = 1000

# 교란 변수: 사용자 활동 수준
activity = np.random.normal(50, 10, n)

# 처리: 활동 수준이 높을수록 새 기능 활성화 확률 증가 (교란!)
prob_treat = 1 / (1 + np.exp(-(activity - 50) / 5))
treatment = np.random.binomial(1, prob_treat)

# 결과: 활동 수준과 처리 모두 engagement에 영향
# 진정한 처리 효과 = 5
engagement = 20 + 0.3 * activity + 5 * treatment + np.random.normal(0, 5, n)

df = pd.DataFrame({
    'activity': activity,
    'treatment': treatment,
    'engagement': engagement
})

# --- Naive comparison (편향됨) ---
naive_effect = df[df.treatment == 1].engagement.mean() - df[df.treatment == 0].engagement.mean()
print(f"Naive estimate (biased): {naive_effect:.2f}")
# 진정한 효과는 5이지만, naive는 ~8 정도 (교란 때문)

# --- DoWhy로 인과 추론 ---
model = CausalModel(
    data=df,
    treatment='treatment',
    outcome='engagement',
    common_causes=['activity']   # DAG에서 교란 변수 지정
)

# DAG 확인
model.view_model()

# 식별: Backdoor criterion 적용
identified_estimand = model.identify_effect()
print(identified_estimand)

# 추정: Propensity Score Matching
estimate_psm = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_matching"
)
print(f"PSM estimate: {estimate_psm.value:.2f}")

# 추정: IPTW
estimate_iptw = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.propensity_score_weighting"
)
print(f"IPTW estimate: {estimate_iptw.value:.2f}")

# 추정: Doubly Robust (Linear Regression)
estimate_lr = model.estimate_effect(
    identified_estimand,
    method_name="backdoor.linear_regression"
)
print(f"Linear Regression estimate: {estimate_lr.value:.2f}")

# --- 반박 테스트 (Refutation) ---
# 랜덤 공통 원인 추가
refute_random = model.refute_estimate(
    identified_estimand,
    estimate_iptw,
    method_name="random_common_cause"
)
print(refute_random)

# Placebo treatment (처리를 랜덤으로 대체)
refute_placebo = model.refute_estimate(
    identified_estimand,
    estimate_iptw,
    method_name="placebo_treatment_refuter",
    placebo_type="permute"
)
print(refute_placebo)

9.3 EconML을 활용한 CATE 추정

from econml.dml import LinearDML
from econml.dr import DRLearner
from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier

# --- Double Machine Learning ---
dml = LinearDML(
    model_y=GradientBoostingRegressor(n_estimators=100),
    model_t=GradientBoostingClassifier(n_estimators=100),
    random_state=42
)

dml.fit(
    Y=df['engagement'].values,
    T=df['treatment'].values,
    X=df[['activity']].values,   # effect modifiers
    W=None                        # additional controls
)

# ATE
ate = dml.ate(X=df[['activity']].values)
print(f"DML ATE: {ate:.2f}")

# CATE: 활동 수준별 처리 효과
cate = dml.effect(X=df[['activity']].values)
print(f"CATE range: [{cate.min():.2f}, {cate.max():.2f}]")

# 신뢰구간
ate_inference = dml.ate_inference(X=df[['activity']].values)
print(f"ATE 95% CI: [{ate_inference.conf_int()[0][0]:.2f}, {ate_inference.conf_int()[0][1]:.2f}]")

# --- Doubly Robust Learner ---
dr = DRLearner(
    model_regression=GradientBoostingRegressor(n_estimators=100),
    model_propensity=GradientBoostingClassifier(n_estimators=100),
    model_final=GradientBoostingRegressor(n_estimators=50),
    random_state=42
)

dr.fit(
    Y=df['engagement'].values,
    T=df['treatment'].values,
    X=df[['activity']].values
)

dr_ate = dr.ate(X=df[['activity']].values)
print(f"DR Learner ATE: {dr_ate:.2f}")

9.4 Propensity Score 수동 구현

from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors

# --- Propensity Score 추정 ---
ps_model = LogisticRegression()
ps_model.fit(df[['activity']], df['treatment'])
df['ps'] = ps_model.predict_proba(df[['activity']])[:, 1]

# --- IPTW 수동 구현 ---
df['weight'] = np.where(
    df['treatment'] == 1,
    1 / df['ps'],
    1 / (1 - df['ps'])
)

# Weighted means
y1_weighted = np.average(
    df.loc[df.treatment == 1, 'engagement'],
    weights=df.loc[df.treatment == 1, 'weight']
)
y0_weighted = np.average(
    df.loc[df.treatment == 0, 'engagement'],
    weights=df.loc[df.treatment == 0, 'weight']
)

ate_iptw_manual = y1_weighted - y0_weighted
print(f"Manual IPTW ATE: {ate_iptw_manual:.2f}")

# --- Balance check: Standardized Mean Difference ---
def smd(x, t, w=None):
    if w is None:
        w = np.ones(len(x))
    x1 = np.average(x[t == 1], weights=w[t == 1])
    x0 = np.average(x[t == 0], weights=w[t == 0])
    s = np.sqrt((x[t == 1].var() + x[t == 0].var()) / 2)
    return (x1 - x0) / s

smd_before = smd(df['activity'].values, df['treatment'].values)
smd_after = smd(df['activity'].values, df['treatment'].values, df['weight'].values)
print(f"SMD before weighting: {smd_before:.3f}")
print(f"SMD after weighting:  {smd_after:.3f}")

10 10. R 실무 예시

10.1 MatchIt을 이용한 매칭

library(MatchIt)
library(cobalt)
library(marginaleffects)

# --- 시뮬레이션 데이터 ---
set.seed(42)
n <- 1000
activity <- rnorm(n, 50, 10)
prob_treat <- plogis((activity - 50) / 5)
treatment <- rbinom(n, 1, prob_treat)
engagement <- 20 + 0.3 * activity + 5 * treatment + rnorm(n, 0, 5)

df <- data.frame(activity, treatment, engagement)

# --- Naive estimate ---
naive <- mean(df$engagement[df$treatment == 1]) -
         mean(df$engagement[df$treatment == 0])
cat("Naive estimate:", round(naive, 2), "\n")

# --- Propensity Score Matching ---
m_out <- matchit(
  treatment ~ activity,
  data = df,
  method = "nearest",         # nearest neighbor
  distance = "glm",           # logistic regression for PS
  ratio = 1,                  # 1:1 matching
  caliper = 0.2               # caliper = 0.2 SD of PS
)

summary(m_out)

# Balance plot
love.plot(m_out, thresholds = 0.1)

# 매칭 데이터에서 효과 추정
m_data <- match.data(m_out)
fit <- lm(engagement ~ treatment, data = m_data, weights = weights)
avg_comparisons(fit, variables = "treatment", newdata = m_data, wts = "weights")

10.2 WeightIt을 이용한 IPTW

library(WeightIt)
library(survey)

# --- IPTW ---
w_out <- weightit(
  treatment ~ activity,
  data = df,
  method = "ps",           # propensity score
  estimand = "ATE"
)

summary(w_out)
bal.tab(w_out, thresholds = 0.1)

# 가중 회귀
d_w <- svydesign(~1, weights = w_out$weights, data = df)
fit_w <- svyglm(engagement ~ treatment, design = d_w)
summary(fit_w)
# treatment coefficient ≈ 5 (true effect)

# --- Doubly Robust ---
w_out_dr <- weightit(
  treatment ~ activity,
  data = df,
  method = "ps",
  estimand = "ATE"
)

df$weights_dr <- w_out_dr$weights
fit_dr <- lm(engagement ~ treatment + activity, data = df, weights = weights_dr)
summary(fit_dr)

10.3 mediation 패키지를 이용한 매개분석

library(mediation)

set.seed(42)
n <- 500
treatment <- rbinom(n, 1, 0.5)
# 매개변수: 체류시간 (treatment → mediator)
dwell_time <- 10 + 3 * treatment + rnorm(n, 0, 2)
# 결과: 만족도 (treatment → outcome, mediator → outcome)
satisfaction <- 50 + 2 * treatment + 1.5 * dwell_time + rnorm(n, 0, 3)
df_med <- data.frame(treatment, dwell_time, satisfaction)

# Step 1: Mediator model
med_fit <- lm(dwell_time ~ treatment, data = df_med)

# Step 2: Outcome model
out_fit <- lm(satisfaction ~ treatment + dwell_time, data = df_med)

# Step 3: Causal mediation analysis
med_result <- mediate(
  med_fit,                   # mediator model
  out_fit,                   # outcome model
  treat = "treatment",
  mediator = "dwell_time",
  boot = TRUE,
  sims = 1000
)

summary(med_result)
# ACME (Average Causal Mediation Effect) ≈ 3 * 1.5 = 4.5
# ADE (Average Direct Effect) ≈ 2
# Total Effect ≈ 6.5
# Proportion Mediated ≈ 4.5 / 6.5 ≈ 0.69

10.4 IV 분석 (AER 패키지)

library(AER)

set.seed(42)
n <- 1000
U <- rnorm(n)                             # unmeasured confounder
Z <- rbinom(n, 1, 0.5)                    # instrument (random encouragement)
T_star <- 0.5 * Z + 0.3 * U + rnorm(n)   # treatment influenced by Z and U
T_binary <- as.integer(T_star > 0)
Y <- 3 * T_binary + 2 * U + rnorm(n)     # true effect = 3, confounded by U

df_iv <- data.frame(Y, T_binary, Z, U)

# Naive OLS (biased due to U)
ols <- lm(Y ~ T_binary, data = df_iv)
cat("OLS estimate:", round(coef(ols)["T_binary"], 2), "\n")

# 2SLS with IV
iv <- ivreg(Y ~ T_binary | Z, data = df_iv)
summary(iv)
cat("IV estimate:", round(coef(iv)["T_binary"], 2), "\n")

# First-stage F-statistic
first_stage <- lm(T_binary ~ Z, data = df_iv)
cat("First-stage F:", round(summary(first_stage)$fstatistic[1], 1), "\n")

11 11. 인과 추론 도구 선택 가이드

아래 의사결정 흐름을 따라 적절한 방법을 선택한다:

처리가 무작위 배정되었는가?
├── YES → RCT 분석 (t-test, 회귀, ANCOVA) → [32편 참조]
└── NO → 관찰 데이터
    │
    ├── 미관측 교란이 있는가?
    │   ├── YES → Instrument 있는가?
    │   │   ├── YES → IV / 2SLS
    │   │   └── NO → Sensitivity Analysis, Bounds
    │   │
    │   └── NO (ignorability 가정 가능)
    │       │
    │       ├── 처리가 시간에 따라 변하는가?
    │       │   ├── YES → G-methods (MSM, G-computation)
    │       │   └── NO (단일 시점 처리)
    │       │       │
    │       │       ├── 처리 할당에 불연속이 있는가?
    │       │       │   ├── YES → RDD [34편 참조]
    │       │       │   └── NO
    │       │       │
    │       │       ├── 정책 도입 시점이 있는가?
    │       │       │   ├── YES → DiD / ITS [19편, 34편 참조]
    │       │       │   └── NO
    │       │       │
    │       │       └── Matching / IPTW / Doubly Robust
    │       │
    │       └── 메커니즘을 분해하고 싶은가?
    │           └── YES → Mediation Analysis
    │
    └── 효과 이질성(CATE)이 궁금한가?
        └── YES → Causal Forest, DML, DR Learner

11.1 방법별 요약 비교

방법 미관측 교란 허용 시간 변동 핵심 가정 Python R
Matching/PSM X X Ignorability DoWhy MatchIt
IPTW X O Positivity, correct PS DoWhy WeightIt
Doubly Robust X O 둘 중 하나 맞으면 됨 EconML AIPW
IV/2SLS O X Exclusion restriction statsmodels AER
G-computation X O Sequential ignorability zEpid gfoRmula
MSM X O Correct weight model zEpid ipw
DML/Causal Forest X X Ignorability EconML grf
Mediation X X Sequential ignorability mediation

12 12. 핵심 요약

12.1 기억할 핵심 원칙

  1. 인과는 가정에서 나온다: 어떤 방법도 “가정 없이” 인과를 증명하지 못한다. DAG로 가정을 명시하고, sensitivity analysis로 가정의 견고성을 확인하라.

  2. 프레임워크는 도구: RCM과 SCM은 경쟁이 아니라 보완이다. DAG로 구조를 그리고, potential outcomes로 추정량을 정의하라.

  3. 이중 견고성을 추구하라: Doubly Robust Estimator는 처리 모델과 결과 모델 중 하나만 맞아도 일치 추정량이다. 가능하면 항상 사용하라.

  4. 시간 변동 교란은 특별하다: 표준 회귀로 해결되지 않는다. 종단 데이터에서 인과 추론이 필요하면 G-methods(G-computation, MSM, G-estimation)를 사용하라.

  5. 진단을 생략하지 마라: Balance check(SMD), weight 분포, overlap 확인은 필수다.

12.2 시리즈 내 관련 파일

파일 주제 연결 포인트
20 — 연구 설계 Overview 전체 연구 설계 계보 인과 추론의 위치
32 — RCT & A/B Test 무작위 배정의 원칙 인과 추론이 불필요한 이상적 경우
33 — 관찰 연구 코호트, 케이스-컨트롤 인과 추론 도구가 필요한 이유
34 — 준실험적 설계 ITS, RDD, Stepped Wedge 자연 실험 기반 인과 추론
19 — DiD Difference-in-Differences 패널 데이터의 인과 추론
08 — GEE Marginal 효과 MSM과의 관계

Subscribe

Enjoy this blog? Get notified of new posts by email: