GBT
한국외국어대학교Global Business & Technology
실습 4 · 인과추론 실습
목차 강의 홈 ↗
대학원 딥러닝 · 실데이터 실습 4

인과추론 실습
DiD · 합성통제 · 반사실 예측 · DML 을 실데이터로

인과추론 시리즈 1~11장의 논리를 네 개의 실데이터 위에서 끝까지 돌린다. 장기기증 등록(DiD), Prop 99(합성통제), 서울 지하철 코로나(딥러닝 반사실), LaLonde(DML·CATE). 모든 숫자는 이 페이지를 만들며 실제로 실행한 결과다.

데이터 4종코드 블록 6그림 6장조작형 위젯 1노트북 04_causal.ipynb
0
1988년 11월, 캘리포니아 — 그리고 어느 월요일 아침

같은 사건, 다른 자리

4장은 이 사건의 논리를 다뤘다. 이번에는 그 논리를 데이터 파일이 열린 터미널 앞에서 끝까지 실행한다.

도입 · 한 장면

prop99.csv 를 열어 놓고 멈춘 손

1988년 11월, 캘리포니아 주민투표에서 담배 한 갑당 25센트를 더 걷어 금연 캠페인에 쓰는 Proposition 99가 통과됐다. 4장의 도입은 여기서 "비교할 두 번째 캘리포니아가 없다면 만들면 된다"는 발상으로 이어졌다. 이번 회차의 장면은 그 뒤에 온다. 세미나가 끝난 다음 월요일 아침, 4장을 읽은 대학원생이 39개 주 × 31년의 prop99.csv 를 열어 놓고 앉아 있다. 가중치가 비음이고 합이 1이어야 한다는 것은 안다. 그런데 그 가중치를 어느 함수로 구하는가. 위약검정의 p 값은 정확히 무엇을 몇으로 나눈 값인가. 결과가 나오면 표에 어느 열을 두는가.

이 간극은 논리를 몰라서가 아니라 절차가 흩어져 있어서 생긴다. 11장이 그 절차를 "식별 → 추정 → 반박"이라는 세 단계로 모았다면, 이 회차는 그 세 단계를 네 개의 실데이터 위에서 실제로 돌린다. 코드는 20행 안팎의 블록 여섯 개이고, 같은 코드가 노트북의 셀로 들어 있다. 페이지의 모든 숫자는 그 코드를 실행해서 얻은 값이며, 원 논문과 다른 곳은 다르다고 적었다.

평균으로 만드는 반사실DiD. 대조 주들의 변화량 평균이 처치 주의 "정책 없는 변화량"을 대신한다. 네 칸 평균, 회귀, 클러스터 표준오차, event study 까지 한 벌
가중조합·예측으로 만드는 반사실합성통제와 딥러닝 예측기. 반사실을 대조 단위의 볼록결합 또는 개입 전 데이터로 학습한 모형의 예측으로 만들고, 표준오차 대신 위약검정과 등각 구간으로 불확실성을 잰다
ML 을 써도 구간이 무너지지 않는 배관DoWhy 로 식별을 강제하고, econml 의 DML 로 nuisance 를 ML 에 맡기되 교차적합과 직교화로 √n 추론을 지키며, 반박 3종으로 결론을 두드린다
더 읽기

네 데이터는 일부러 "답이 알려진" 것과 "답을 모르는" 것을 섞었다. LaLonde 의 NSW 표본은 무작위 실험이라 단순 평균 차이 1,794달러가 벤치마크이고, Prop 99 는 원 논문의 가중치와 격차가 공개돼 있어 직접 구현한 결과를 대조할 수 있다. 반면 장기기증 데이터는 교과서(The Effect)의 예제이지만 사전 추세 계수 하나가 0에서 벗어나 있고, 서울 지하철은 우리가 직접 만든 데이터라 아무도 정답을 모른다. 답이 있는 데이터로 절차를 검증하고, 답이 없는 데이터에서 절차가 무엇을 말해 주고 무엇을 말해 주지 못하는지 보는 것이 이 회차의 구성이다.

생각해 볼 질문

본인 연구의 처치는 "여러 단위 중 일부"인가, "단위 하나"인가, "개인 횡단면의 더미"인가. 이 답에 따라 이 회차의 §2, §3~4, §5 중 어느 하나가 본인 논문의 방법 절이 된다.

출처 인과추론 4장 반사실 시계열 예측 · 11장 도구 지형도와 읽기 일정 · Abadie, Diamond & Hainmueller, "Synthetic Control Methods for Comparative Case Studies", JASA 105(490), 2010, doi.org/10.1198/jasa.2009.ap08746

1
11장의 최소 워크플로 · 네 개의 실데이터

식별 → 추정 → 반박, 그리고 데이터 4종

어떤 방법을 쓰든 세 단계의 골격은 같다. 네 데이터는 각각 "처치 단위·처치 시점·결과변수"가 다르게 생겼고, 그 모양이 방법을 정한다.

데이터 요약

네 데이터의 모양이 네 방법을 고른다

처치 단위가 여럿이고 시점이 짧으면 DiD, 처치 단위가 하나이고 대조 시계열이 길면 합성통제, 대조 시계열조차 없으면 예측기, 개인 횡단면이면 DML 이다. 방법을 먼저 고르고 데이터를 맞추는 것이 아니라 그 반대다.

데이터출처 · 접근크기처치 단위 / 시점결과변수방법
organ_donationscausaldata 패키지 (Huntington-Klein, The Effect; 원 연구 Kessler & Roth 2014)27개 주 × 6분기 = 162행캘리포니아 / 2011Q3 (등록 질문을 'active choice' 로 변경)장기기증 등록률 (0~1)DiD
Prop 99synthdid 저장소 CSV (구분자 ;)39개 주 × 31년 = 1,209행캘리포니아 / 1989년1인당 연간 담배 소비 (갑)합성통제
서울 지하철 일별 총승차서울열린데이터광장 역별 일별 승하차 → 일별 합계 (실습 1과 같은 파일)2015-01-01 ~ 2026-08-31, 4,261일서울 전체 / 2020-02-23 (위기경보 '심각'), 2024-01-27 (기후동행카드)일 승차 인원 (백만 명)반사실 예측
LaLonde (NSW)dowhy.datasets.lalonde_dataset() = Dehejia & Wahba 실험표본; 관측 버전은 NBER 의 PSID 대조군445명 (처치 185) / 관측 버전 2,675명개인 / 직업훈련 참여 더미1978년 소득 (달러)DML · CATE
LAB · 코드형

데이터 4종 로드

전처리는 거의 없다. 지하철은 실습 1에서 만든 일별 합계 CSV 를 그대로 쓰고, 나머지는 패키지나 공개 URL 에서 바로 읽는다.

# pip install dowhy econml causaldata holidays (Colab: 첫 셀에서 설치) import numpy as np, pandas as pd from causaldata import organ_donations import dowhy.datasets od = organ_donations.load_pandas().data # 27개 주 × 6분기 (2010Q4~2012Q1), 장기기증 등록률 p99 = pd.read_csv("https://raw.githubusercontent.com/synth-inference/synthdid/master/data/california_prop99.csv", sep=";") sub = pd.read_csv("https://hufs-ai-lecture.pages.dev/practice/data/subway_daily_total.csv", parse_dates=["date"]) la = dowhy.datasets.lalonde_dataset() # NSW 실험표본 445명 (Dehejia & Wahba 1999) print(od.shape, p99.shape, sub.shape, la.shape) # → (162, 4) (1209, 4) (4261, 4) (445, 12)

관찰 포인트 — 네 데이터에서 각각 "처치 단위·처치 시점·결과변수"가 무엇인지 말할 수 있으면 로드는 끝난 것이다. LaLonde 의 treat 가 실험 배정이라는 사실이 §5 의 벤치마크를 만든다.

세 단계, 한 줄씩

식별 — 무엇을 가정하면 반사실이 관측 가능한 양으로 바뀌는가. DiD 는 평행추세, 합성통제는 볼록결합 안의 사전 적합, 예측기는 "개입 전 관계의 유지", DML 은 무교란(backdoor).

추정 — 그 가정 아래 계산하는 숫자와 불확실성. 클러스터 SE, 위약 순위, 등각 구간, 교차적합 영향함수.

반박 — 결론이 틀렸다면 드러났어야 할 검정. 사전 추세, in-space·in-time 위약, 위약 처치·가짜 교란·부분표본.

출처 Huntington-Klein, The Effect, ch. 18 (Difference-in-Differences), theeffectbook.net · synthdid 데이터, github.com/synth-inference/synthdid · Dehejia 데이터 페이지, users.nber.org/~rdehejia/nswdata2.html · 서울열린데이터광장 지하철 역별 일별 승하차, data.seoul.go.kr (OA-12914)

2
1·2장의 실습 · organ_donations

DiD: 평균으로 만드는 반사실

네 칸 평균과 상호작용항은 소수점까지 같은 −0.0225 를 낸다. 달라지는 것은 표준오차이고, 그것을 결정하는 것은 클러스터다.

추정

2×2 → 회귀 → 주 클러스터 표준오차

한 줄 목표: 네 칸 평균, 상호작용 회귀, 이원 고정효과 회귀가 같은 점추정을 내는 것을 확인하고, 표준오차만 클러스터에 따라 스무 배 차이 나는 것을 본다.

2011년 7월 캘리포니아는 운전면허 갱신 시 장기기증 등록 질문을 "예/아니오를 반드시 고르는" active choice 방식으로 바꿨다. 등록률이 오르리라는 기대와 달리, 캘리포니아의 등록률은 다른 26개 주가 조금 오르는 동안 오히려 내려갔다.

LAB · 코드형

DiD 회귀 한 블록

1장 §4의 회귀식 \(Y_{it} = \alpha + \beta\,\text{Treat}_i + \gamma\,\text{Post}_t + \delta\,(\text{Treat}_i \times \text{Post}_t) + \varepsilon_{it}\) 을 그대로 옮긴다. 마지막 다섯 줄이 event study 다.

import statsmodels.formula.api as smf od["Treat"] = (od.State == "California").astype(int) od["Post"] = (od.Quarter_Num >= 4).astype(int) # 2011Q3 부터 처치 g = od.groupby(["Treat", "Post"]).Rate.mean() print((g[1,1] - g[1,0]) - (g[0,1] - g[0,0])) # → -0.0225 (네 칸 평균) m = smf.ols("Rate ~ Treat*Post", data=od).fit( cov_type="cluster", cov_kwds={"groups": od.State}) # 주 클러스터 SE (27개 주) print(m.params["Treat:Post"], m.bse["Treat:Post"]) # → -0.0225, 0.0061 # event study: 기준 분기 2011Q2 (Quarter_Num 3) 를 빼고 처치군×분기 더미 for k in [1, 2, 4, 5, 6]: od[f"T_q{k}"] = od.Treat * (od.Quarter_Num == k) es = smf.ols("Rate ~ C(State) + C(Quarter_Num) + T_q1 + T_q2 + T_q4 + T_q5 + T_q6", data=od).fit(cov_type="cluster", cov_kwds={"groups": od.State}) print(es.params.filter(like="T_q").round(4)) # → T_q1 -0.0029 T_q2 0.0063 T_q4 -0.0216 T_q5 -0.0203 T_q6 -0.0222

관찰 포인트 — 세 방법의 점추정이 같으면 성공. 이어서 fit() 을 클러스터 옵션 없이 돌려 표준오차가 얼마나 달라지는지 보라. 열린 과제: 클러스터를 Quarter_Num 으로 잘못 잡으면 SE 가 어떻게 되는가.

추정계수 \(\hat\delta\)SE95% CI비고
네 칸 평균−0.0225(0.2628−0.2713) − (0.4589−0.4449)
Treat×Post 회귀 · iid SE−0.02250.1253주 간 수준 차이가 오차항에 남아 SE 가 부풀려짐
Treat×Post 회귀 · 주 클러스터−0.02250.0061[−0.0344, −0.0106]27개 클러스터, p = 0.0002
주·분기 이원 고정효과 · 주 클러스터−0.02250.00672×2 에서는 점추정이 같다 (3장)

실행 2026-09-12 · statsmodels 0.15.0 · 등록률은 0~1 비율이므로 −0.0225 = −2.25 %p

캘리포니아와 다른 26개 주 평균의 분기별 장기기증 등록률. 처치 후 캘리포니아만 내려간다.
그림 1. 캘리포니아(브릭)는 출발 수준이 대조군(틸)보다 17 %p 낮지만, 처치 전 세 분기 동안 두 선의 기울기는 거의 평행하다. 회색 점선은 대조군의 변화량을 캘리포니아의 2011Q2 값에 얹은 반사실이고, 처치 후 관측선과 점선의 간격이 −2.25 %p 다. 수준 차이 \(\hat\beta = -0.174\) 는 첫 번째 차분에서 사라지므로 추정에 해가 없다.
더 읽기

iid 표준오차 0.125 는 이 데이터에서 특히 극단적이다. 주 고정효과 없이 Treat*Post 만 넣으면 주 간 수준 차이(예: 알래스카 0.75 대 애리조나 0.26)가 전부 오차항으로 들어가고, 그 분산이 표준오차 공식에 그대로 실린다. 클러스터 SE 는 같은 주 안의 관측치를 묶어 "주 27개"를 유효 표본으로 취급하므로 이 오염이 사라진다. 이원 고정효과 모형(마지막 행)은 수준 차이를 모형 안에서 제거해 두 SE 가 가까워진다.

클러스터 27개는 통상의 "40개 미만" 경고선 아래다. 1장에서 말한 와일드 클러스터 부트스트랩이 원칙적으로는 더 안전하며, 처치 클러스터가 캘리포니아 하나뿐이라는 점은 더 근본적인 문제다(1장 §4의 "처치군 클러스터가 한두 개뿐인 극단"). 다음 절의 합성통제 순열검정이 바로 이 상황에 대한 답이다. 같은 데이터에 두 방법을 모두 적용해 보는 것이 자연스러운 열린 과제다.

출처 Kessler & Roth, "Don't Take 'No' for an Answer: An Experiment with Actual Organ Donor Registrations", NBER WP 20378, 2014, nber.org/papers/w20378 · Huntington-Klein, The Effect, ch. 18, theeffectbook.net · Bertrand, Duflo & Mullainathan, QJE 119(1), 2004 (클러스터링).

반박 · 사전 추세

event study: 처치 전 계수가 0 인가

처치 후 세 분기의 계수는 −0.020 ~ −0.022 로 안정적이고 95% 구간이 모두 0 을 제외한다. 처치 전 두 계수 중 하나(2011Q1, +0.006)는 작지만 0 을 제외해, 평행추세의 정황 증거에 흠이 하나 있다.

event study: 기준 분기 2011Q2 대비 처치군×분기 계수와 95% 신뢰구간
그림 2. 기준 분기 2011Q2 를 0 으로 두고 나머지 다섯 분기의 처치군×분기 계수를 그렸다. 처치 후 세 점이 같은 높이(약 −0.02)에 나란히 놓인 것이 효과의 지속을 말하고, 처치 전 2011Q1 의 +0.006(구간 [0.001, 0.011])이 이 설계의 약점이다. 2011Q1 에 캘리포니아가 대조군보다 0.6 %p 더 올랐다는 뜻인데, 처치 후 하락폭 2 %p 에 비하면 작지만 무시할 수는 없다.
분기2010Q42011Q12011Q22011Q32011Q42012Q1
계수−0.0029+0.00630 (기준)−0.0216−0.0203−0.0222
클러스터 SE0.00560.00250.00550.00490.0110
더 읽기

사전 계수 하나가 유의하다고 설계를 버릴 일은 아니지만, 2장이 말한 대로 "사전 추세 검정 통과"를 평행추세의 증명으로 쓸 수도 없다. 정직한 보고는 세 가지다. 첫째, 계수 표를 그대로 싣는다. 둘째, 사전 계수의 크기(0.6 %p)와 효과 크기(2 %p)의 비를 적어 독자가 판단하게 한다. 셋째, 2장의 민감도 분석(Rambachan & Roth 의 "사전 추세의 몇 배까지 위반을 허용하면 결론이 뒤집히는가")을 부록에 둔다. 이 데이터는 사전 시점이 세 개뿐이라 그 분석의 힘도 약하다는 점까지 적으면 한계 절이 완성된다.

원 연구(Kessler & Roth 2014)의 결론도 같은 방향이다. "예/아니오를 강제로 고르게" 하는 방식이 등록을 늘리지 못했고 오히려 줄였다는 결과이며, 저자들은 이를 'active choice' 가 사실상 "아니오"를 기본값으로 만드는 효과로 해석한다.

출처 Roth, "Pretest with Caution: Event-Study Estimates after Testing for Parallel Trends", AER: Insights 4(3), 2022 · Rambachan & Roth, "A More Credible Approach to Parallel Trends", Review of Economic Studies 90(5), 2023 · 인과추론 2장 평행추세는 검증할 수 있는가

3
4장의 실습 · Prop 99 · 직접 구현

합성통제: 가중조합으로 만드는 반사실

scipy 스무 줄로 푼 가중치는 원 논문과 같은 주에 몰렸고, 2000년 격차 −26.6갑도 원 논문의 약 26갑과 겹친다. 위약 순위는 39개 중 3위로, 원 논문보다 보수적이다.

추정

비음·합 1 가중치를 scipy 로 푼다

한 줄 목표: 4장 §3의 최적화 문제 \(\min_w \sum_{t \le 1988} (Y_{1t} - \sum_j w_j Y_{jt})^2,\; w_j \ge 0,\; \sum_j w_j = 1\) 을 scipy.optimize.minimize 로 직접 풀고, 가중치 표와 사전 RMSPE 를 얻는다.

LAB · 코드형

합성통제 최적화 한 블록

결과변수의 사전 경로만으로 적합한다(원 논문은 소득·맥주 소비 등 예측변수를 함께 쓴다). SLSQP 에 비음 경계와 합 1 등식 제약을 준다.

from scipy.optimize import minimize Y = p99.pivot(index="Year", columns="State", values="PacksPerCapita") # 31년 × 39주 pre = np.asarray(Y.index <= 1988) def sc_weights(y1, Y0): # 비음·합 1 가중치, 사전 RMSPE 최소화 J = Y0.shape[1] obj = lambda w: np.mean((y1 - Y0 @ w) ** 2) jac = lambda w: -2 * Y0.T @ (y1 - Y0 @ w) / len(y1) r = minimize(obj, np.full(J, 1/J), jac=jac, method="SLSQP", bounds=[(0, 1)]*J, constraints={"type": "eq", "fun": lambda w: w.sum() - 1}, options={"ftol": 1e-14, "maxiter": 2000}) w = np.clip(r.x, 0, None); return w / w.sum() def synth(unit, donors): w = sc_weights(Y[unit].values[pre], Y[donors].values[pre]) gap = Y[unit].values - Y[donors].values @ w # 관측 − 합성 rmspe = lambda mask: np.sqrt(np.mean(gap[mask] ** 2)) return w, gap, rmspe(pre), rmspe(~pre) donors = [s for s in Y.columns if s != "California"] w, gap, pre_r, post_r = synth("California", donors) print(pd.Series(w, donors).sort_values(ascending=False).head(5).round(3)) # → Utah 0.394 Montana 0.232 Nevada 0.205 Connecticut 0.109 New Hampshire 0.045 print(pre_r, post_r, gap[~pre].mean(), gap[-1]) # → 1.66, 20.6, -19.5, -26.6

관찰 포인트 — 가중치가 0.01 을 넘는 주가 여섯 개뿐이면 성공. 볼록결합 제약이 만드는 희소성이다. 열린 과제: 제약을 풀고(경계·등식 제거) 같은 문제를 최소제곱으로 풀면 가중치에 음수가 생기고 사전 RMSPE 는 더 작아진다. 그 반사실을 믿을 수 있는가.

대조 주이 실습 (결과변수만)Abadie 외 2010 (예측변수 포함)
유타0.3940.334
몬태나0.2320.199
네바다0.2050.234
코네티컷0.1090.069
뉴햄프셔0.045
콜로라도0.0150.164
사전 RMSPE (1970–88)1.66갑(논문 표기 없음)
1989–2000 평균 격차−19.5갑약 −19갑 (본문 서술 기준)
2000년 격차−26.6갑약 −26갑

실행 2026-09-12 · scipy 1.16.3 SLSQP · 가중치 합 1.000, 최솟값 0

캘리포니아 관측 담배 소비와 합성 캘리포니아의 1970~2000년 경로
그림 3. 1970~1988년 사전 구간에서 관측(브릭)과 합성(회색 점선)이 거의 겹친다(RMSPE 1.66갑). 1989년 이후 두 경로가 벌어져 2000년에는 26.6갑 차이가 난다. 합성 경로가 1990년대에도 완만히 내려가는 것은 전국적 감소 추세를 대조 주들이 실어 오기 때문이고, 그 추세를 뺀 나머지가 정책 효과로 읽힌다.
더 읽기

원 논문과 가중치가 조금 다른 이유는 두 가지다. 첫째, 원 논문은 결과변수의 일부 연도 값과 함께 소득·맥주 소비·청년 인구 비율·소매가격 같은 예측변수를 쓰고, 예측변수의 중요도 행렬 \(V\) 를 교차검증으로 고른다. 둘째, 이 실습은 19개 사전 연도 전부를 같은 비중으로 맞춘다. 그럼에도 상위 세 주(유타·몬태나·네바다)가 같고 사후 격차가 겹친다는 것이 이 방법의 안정성을 보여 준다. 반사실이 "누구로 만들어졌는지"가 표 한 장으로 공개되기 때문에 심사자와의 논쟁이 통계가 아니라 실질("유타가 캘리포니아의 40%라는 게 말이 되는가")로 옮겨 간다.

사전 RMSPE 1.66갑은 캘리포니아의 사전 평균 소비 약 120갑의 1.4% 다. 이 숫자를 논문에 적는 이유는 다음 카드의 위약검정에서 "사전 적합이 나쁜 주를 제외"할 기준이 되기 때문이다.

출처 Abadie, Diamond & Hainmueller, JASA 105(490), 2010, §3 (Prop 99 결과·가중치 표), doi.org/10.1198/jasa.2009.ap08746 · Abadie, "Using Synthetic Controls", JEL 59(2), 2021, doi.org/10.1257/jel.20191450

반박 · in-space 위약

38개 주를 차례로 "처치"로 두면

사후/사전 RMSPE 비율로 줄을 세우면 캘리포니아는 39개 주 중 3위(p = 3/39 = 0.077)다. 사전 적합이 캘리포니아의 2배보다 나쁜 주를 빼고 사후 격차 크기로 세우면 29개 중 1위(p = 0.034)가 된다. 두 숫자를 모두 적는 것이 정직한 보고다.

왼쪽: 39개 주의 격차 경로(캘리포니아 브릭, 나머지 회색). 오른쪽: 사후/사전 RMSPE 비율 순위 막대
그림 4. 왼쪽 회색 선은 "이 주가 1989년에 처치받았다면"으로 같은 절차를 돌린 38개 주의 격차 경로다. 사전 구간에서 크게 벗어난 회색 선들이 몇 개 보이는데, 이들은 볼록결합으로 재현이 안 되는 주(뉴햄프셔·유타처럼 대조 주들의 볼록포 바깥에 있는 주)다. 오른쪽 비율 순위에서 캘리포니아(브릭)보다 위에 있는 미주리와 버지니아는 사전 적합이 매우 좋아(RMSPE 0.4~0.5갑) 작은 사후 격차도 비율이 커진 경우다. 비율 검정이 "사전 적합이 좋은 주에게 불리"한 이유이며, 다음 위젯에서 직접 확인할 수 있다.
검정 방식비교 집합캘리포니아 순위p = 순위 / 집합 크기
사후/사전 RMSPE 비율39개 주 전부30.077
사후 평균 격차 크기, 사전 RMSPE > 20× 제외3830.079
사후 평균 격차 크기, 사전 RMSPE > 5× 제외3520.057
사후 평균 격차 크기, 사전 RMSPE > 2× 제외2910.034
더 읽기

원 논문의 비율 검정에서는 캘리포니아가 39개 주 중 가장 극단이었다(p = 1/39 ≈ 0.026). 이 실습에서 3위로 밀린 것은 예측변수 없이 결과변수만으로 적합한 결과 미주리·버지니아의 사전 적합이 원 논문보다 좋아졌기 때문이다. 이런 차이는 감출 것이 아니라 표로 적을 것이다. 위약검정의 p 는 표본 추출의 확률이 아니라 "배정이 무작위였다면"이라는 벤치마크에 대한 기술적 검정이므로(4장 §3), 0.077 과 0.034 사이의 차이를 유의성의 경계로 읽기보다 "제외 기준에 따라 이 정도 움직인다"는 민감도로 읽는 것이 맞다.

제외 기준이 사전 RMSPE 에만 의존한다는 점이 이 관례를 "유리한 단위만 남기기"와 구분해 준다. 사후 격차를 보고 단위를 버리면 그것은 결과를 보고 표본을 고르는 일이다.

출처 Abadie, Diamond & Hainmueller, JASA 105(490), 2010, §3.3 (placebo 검정, Fig. 4–7) · 인과추론 4장 §3 위약 순열검정

실습 · 조작형

위약 순열 뷰어: 39개 주를 하나씩 처치로

한 줄 목표: 순위표의 숫자 하나하나가 어떤 경로에서 나왔는지 보고, "비율이 큰 주"와 "격차가 큰 주"가 같지 않다는 것을 눈으로 확인한다.

LAB · 조작형

위약 순열 뷰어

39개 주의 격차 경로(관측 − 합성)가 Python 결과에서 그대로 들어 있다. 주를 고르면 그 주의 경로가 강조되고 사전·사후 RMSPE, 비율, 순위, 상위 가중치 세 개가 표시된다.

캘리포니아 선택한 위약 주 나머지 위약 주

조작 안내 — ① 목록 맨 위의 미주리와 버지니아를 골라 사전 RMSPE 와 사후 평균 격차를 읽어 보라. ② 맨 아래의 뉴햄프셔·유타를 골라 사전 구간에서 경로가 어떻게 생겼는지 보라. ③ 필터를 켜서 남는 주 수와 캘리포니아의 필터 내 순위를 확인하라.
관찰 포인트 — 미주리는 사후 평균 격차가 +9갑에 불과한데 비율이 캘리포니아의 두 배다. 사전 RMSPE 가 0.44갑으로 작기 때문이다. 이것이 보이면 첫 관찰 성공. 뉴햄프셔(사전 RMSPE 58.6갑)는 어떤 볼록결합으로도 맞출 수 없는 주라 그 사후 격차는 효과가 아니라 적합 실패의 잔재라는 것, 그리고 필터를 켜도 캘리포니아의 비율 순위는 3위 그대로(29개 중, p = 0.103)라는 것이 두 번째 관찰이다. 순위표의 p 는 이렇게 검정 방식과 제외 기준의 함수다.

더 읽기

위젯의 숫자는 페이지를 만든 Python 스크립트의 출력을 JSON 으로 내장한 것이고, 사후 평균 격차와 필터 내 순위만 브라우저에서 다시 계산한다. 캘리포니아를 골랐을 때 사전 RMSPE 1.656, 사후 20.606, 비율 12.44, 순위 3/39, 사후 평균 격차 −19.51갑, 상위 가중치 유타 0.394·몬태나 0.232·네바다 0.205 가 나오면 Python 결과와 일치한다.

출처 경로 데이터는 이 페이지의 40_causal_experiment.py 실행 결과(synthdid 의 california_prop99 사용). 절차는 Abadie, Diamond & Hainmueller (2010).

4
4장 §6의 실습 · 서울 지하철 · 두 규칙

딥러닝 반사실: 예측으로 만드는 반사실

코로나가 없었다면 2020년 2~12월 서울 지하철 승차는 약 8억 명 더 많았을 것이다(−31%). 같은 절차를 기후동행카드에 적용하면 격차가 구간 안에 들어와 검출되지 않는다.

설계

예측기는 부품, 규칙은 둘

한 줄 목표: 개입 전 데이터로만 학습한 예측기(릿지 기준선과 MLP)로 개입 후를 예측하고, 위약 시점 검정의 잔차로 등각 구간을 만든다. 4장 §6의 두 규칙 — 학습 데이터의 경계, 불확실성 정량화 — 을 코드 수준에서 지킨다.

규칙 ① 학습은 개입 전만2020-01-31 까지로 학습하고, 조기종료용 검증 구간도 학습 구간의 마지막 10%(2019년 후반)로 잡는다. 정규화 통계량도 학습 구간에서만 계산한다.
규칙 ② 구간은 위약 시점에서2019-02-23 을 가짜 개입으로 두고 2019-01-31 까지만 학습해 2019-02~2020-01 을 예측한다. 이 잔차의 분포가 "개입이 없어도 예측이 빗나가는 정도"이고, 그 분위수가 등각 구간이 된다.
LAB · 코드형

반사실 예측기 한 블록

입력은 요일 원핫 7, 공휴일 1(holidays 패키지), 연중 Fourier 3차 6, 선형 추세 1 의 15개 특징. 릿지가 기준선이고 같은 입력의 2층 MLP 가 딥러닝 예측기다.

import torch, torch.nn as nn, holidays from sklearn.linear_model import Ridge kr = holidays.KR(years=range(2015, 2027)); t0 = sub.date.min() def feats(d): # 요일 7 + 공휴일 1 + 연중 Fourier 3차 6 + 선형 추세 1 = 15 doy = d.dt.dayofyear.values / 365.25 X = [np.eye(7)[d.dt.dayofweek.values], np.array([x in kr for x in d.dt.date])[:, None]] for k in (1, 2, 3): X += [np.sin(2*np.pi*k*doy)[:, None], np.cos(2*np.pi*k*doy)[:, None]] return np.hstack(X + [((d - t0).dt.days.values / 365.25)[:, None]]).astype(float) def fit_mlp(Xtr, ytr, Xte, seed, epochs=600, patience=40): torch.manual_seed(seed); mu, sd = Xtr.mean(0), Xtr.std(0) + 1e-8; ym, ys = ytr.mean(), ytr.std() Xt, yt = torch.tensor((Xtr-mu)/sd).float(), torch.tensor((ytr-ym)/ys).float() net = nn.Sequential(nn.Linear(15,128), nn.ReLU(), nn.Linear(128,128), nn.ReLU(), nn.Linear(128,1)) opt = torch.optim.AdamW(net.parameters(), lr=2e-3, weight_decay=1e-3) nv = len(Xt) // 10; best, bad = (1e9, None), 0 # 조기종료 검증 = 학습구간 마지막 10% (모두 개입 전) for ep in range(epochs): for b in torch.randperm(len(Xt) - nv).split(256): opt.zero_grad(); loss = ((net(Xt[b]).squeeze() - yt[b])**2).mean(); loss.backward(); opt.step() vl = ((net(Xt[-nv:]).squeeze() - yt[-nv:])**2).mean().item() if vl < best[0]: best, bad = (vl, {k: v.clone() for k, v in net.state_dict().items()}), 0 elif (bad := bad + 1) >= patience: break net.load_state_dict(best[1]) return net(torch.tensor((Xte-mu)/sd).float()).squeeze().detach().numpy() * ys + ym

관찰 포인트nv 로 떼는 검증 구간이 학습 구간 안에 있다는 것, mu, sdXtr 에서만 온다는 것이 규칙 ①이다. 열린 과제: 검증 구간을 개입 후로 옮겨 조기종료를 하면 2020년 격차가 어느 방향으로 얼마나 줄어드는가(효과가 "정상 패턴"으로 학습되는 누수).

LAB · 코드형

위약 → 본 분석 → 등각 구간

위약과 본 분석이 같은 함수 한 번씩이다. 구간은 위약 잔차의 30일 이동합 분포에서 5·95% 분위수를 읽어 월 합계에 씌운다.

def counterfactual(train_end, p0, p1, seeds=(0, 1, 2), data=sub): tr, te = data[data.date <= train_end], data[(data.date >= p0) & (data.date <= p1)] Xtr, Xte, y = feats(tr.date), feats(te.date), tr.board.values / 1e6 # 백만 명 out = te[["date"]].assign(obs=te.board.values / 1e6, ridge=Ridge(1.0).fit(Xtr, y).predict(Xte)) out["mlp"] = np.mean([fit_mlp(Xtr, y, Xte, s) for s in seeds], axis=0) return out plc = counterfactual("2019-01-31", "2019-02-01", "2020-01-31") # 위약: 2019-02-23 을 가짜 개입으로 main = counterfactual("2020-01-31", "2020-02-01", "2021-12-31") # 본 분석: 2020-02-23 코로나 '심각' r = (plc.obs - plc.mlp).values # 위약 연도 잔차 → 등각 구간의 재료 lo, hi = np.quantile(np.convolve(r, np.ones(30), "valid"), [.05, .95]) # 30일 합 잔차의 5·95% 분위수 mo = main.assign(gap=main.obs - main.mlp, ym=main.date.dt.to_period("M")).groupby("ym").agg(gap=("gap", "sum"), n=("gap", "size")) mo["lo"], mo["hi"] = mo.gap + lo * mo.n / 30, mo.gap + hi * mo.n / 30 # 월별 격차와 90% 구간 y20 = main[main.date.dt.year == 2020] print(f"2020년 2~12월 손실 {(y20.obs - y20.mlp).sum():.0f}백만 명 ({100*(y20.obs - y20.mlp).sum()/y20.mlp.sum():.1f}%)") print(f"위약 연도 누적 잔차 {r.sum():+.0f}백만 명 ({100*r.sum()/plc.obs.sum():+.1f}%) 30일합 90% 구간 [{lo:.1f}, {hi:.1f}]") # → 2020년 손실 약 -797백만 명 (-31%) · 위약 연도 잔차 +38 (+1.4%) · 구간 [-0.7, 8.1]

관찰 포인트 — 위약 연도 누적 잔차(+38백만 명, +1.4%)가 2020년 격차(−797백만 명)의 5% 미만이면 성공. 30일 합 구간 [−0.7, +8.1] 이 0 을 중심으로 대칭이 아닌 것도 봐 두라. 모형이 2019년을 1.4% 과소예측했다는 뜻이고, 그 편향이 구간에 그대로 들어간다.

출처 인과추론 4장 §6 딥러닝 확장: 예측기 교체의 두 규칙 · Brodersen 외, "Inferring Causal Impact Using Bayesian Structural Time-Series Models", Annals of Applied Statistics 9(1), 2015 (반사실 예측 설계의 원형) · Chernozhukov, Wüthrich & Zhu, "An Exact and Robust Conformal Inference Method for Counterfactual and Synthetic Controls", JASA 116(536), 2021.

결과

2020년 손실 승차 약 8억 명, 구간은 그 10분의 1 폭

릿지와 MLP 는 위약 연도에서 같은 방향으로 1.4% 빗나갔고, 2020년 2~12월 손실을 각각 −741 / −797백만 명(−30% / −31%)으로 추정한다. 두 예측기의 차이(56백만 명)가 등각 구간의 폭과 같은 규모라는 것이 이 결과의 정직한 해석이다.

예측기위약 연도 일별 RMSE위약 연도 누적 잔차30일 합 90% 구간2020년 2~12월 관측 / 반사실손실 (90% 구간)2021년 손실
릿지 (기준선)0.49+38.6 (+1.4%)[−1.9, +10.1]1,761 / 2,501−741 [−762, −628]−711
MLP 2×128 (seed 5회)0.37+38.4 (+1.4%)[−0.7, +8.1]1,761 / 2,557−797 [−804, −706]−803

단위 백만 명 · 실행 2026-09-12 · RTX 4090 · torch 2.11 · MLP seed 0–4 (seed 간 일별 예측 표준편차 평균 0.087) · 연간 구간 = 월 구간을 완전상관 가정으로 합산(보수적)

위: 2019~2021 일 승차 관측과 두 반사실 및 등각 구간. 아래: 월별 격차 막대와 90% 구간, 위약 연도 월별 잔차
그림 5. 위 그림에서 반사실(회색 점선 MLP, 민트 점선 릿지)은 2020년 이후에도 2019년의 계절 패턴을 그대로 이어 간다. 관측(브릭)은 2020년 3월, 8월 말, 12월의 유행 파동마다 내려앉는다. 아래 그림의 틸 막대는 위약 연도(2019-02~2020-01)의 월별 잔차로, 최대 +8백만 명이고 대부분 0 근처다. 브릭 막대(2020년 이후 월별 격차)는 −40 ~ −100백만 명이고, 90% 구간(짧은 세로선)이 0 에서 멀다. 효과가 잡음의 열 배를 넘는 사례다.
더 읽기

이 결과에서 딥러닝은 릿지를 "이겼는가". 위약 연도 일별 RMSE 는 MLP 가 0.37 로 릿지 0.49 보다 낫지만, 누적 잔차는 둘 다 +38 로 같다. 요일·공휴일의 비선형 상호작용(예: 연휴 전날)은 MLP 가 더 잘 잡지만, 연 단위 수준을 결정하는 것은 선형 추세 항이고 그것은 두 모형이 공유한다. 반사실의 핵심 불확실성이 "추세를 얼마나 멀리 외삽하느냐"에 있는 문제라면 모형 용량은 부차적이다. 4장이 "사전 구간이 짧으면 구조를 아는 작은 모형이 유리하다"고 한 것의 데이터 판이다.

구간의 한계도 적어 둔다. 위약 연도는 하나뿐이라 30일 합 잔차의 분포는 336개의 겹치는 창에서 나왔고, 연간 손실의 구간은 월 구간을 완전상관으로 더한 보수적 근사다. 효과가 이렇게 크면 구간의 정밀도는 결론을 바꾸지 않지만, 다음 카드처럼 효과가 작은 문제에서는 이 구간의 폭이 곧 검출력이다. 또 2021년 손실(−803)이 2020년과 같은 규모인 것은 2년 뒤까지 추세를 외삽한 결과이며, 외삽이 길어질수록 "코로나가 없었을 2021년"의 반사실 자체가 약해진다.

출처 질병관리청, 코로나19 위기경보 '심각' 격상(2020-02-23) 보도자료, kdca.go.kr (보도자료) · Angelopoulos & Bates, "Conformal Prediction: A Gentle Introduction", Foundations and Trends in ML 16(4), 2023, arxiv.org/abs/2107.07511

같은 절차, 작은 개입

기후동행카드(2024-01-27): 검출 불가 격차가 구간 안

2022~2023년으로 학습해 2024년 2~12월을 예측하면 격차는 MLP −2.7%, 릿지 −5.4% 이지만, 11개월 모두 90% 구간이 0 을 포함한다. 이 설계로는 효과를 검출할 수 없다는 것이 결과다.

예측기위약(2023) 누적 잔차30일 합 90% 구간2024년 2~12월 격차 (90% 구간)구간이 0 을 포함한 달
릿지−2.2%[−19.4, +27.0]−133 (−5.4%) [−350, +169]11 / 11
MLP (seed 5회)+0.3%[−16.0, +29.4]−64 (−2.7%) [−242, +265]11 / 11

학습 2022-01-01 ~ 2023-12-31 (추세 원점 2022-01-01) · 위약: 2023-01-27 가짜 개입, 2022년만 학습 → 2023-02~12 예측

더 읽기

왜 검출이 안 되는가. 첫째, 학습 구간이 2년뿐이고 그 2년이 코로나 회복기라 선형 추세가 강하게 위로 기울어 있다. 위약 검정에서 이 추세를 1년 더 외삽하면 30일 합 잔차가 ±20~30백만 명(월 승차의 약 10%)까지 흔들리고, 그 폭이 그대로 구간이 된다. 둘째, 기대되는 효과 자체가 작다. 정기권 한 장이 서울 전체 승차를 몇 % 이상 바꾸리라 기대하기 어렵다. 잡음이 10% 인 도구로 2~3% 효과를 찾는 셈이다.

이 결과를 "효과가 없다"로 쓰면 안 된다. "이 설계의 검출력으로는 ±10% 안의 효과를 구별할 수 없다"가 맞는 문장이고, 검출력을 높이는 길은 모형이 아니라 설계에 있다. 카드 이용이 집중되는 역·시간대를 처치 단위로 좁히고 나머지를 대조 시계열로 넣는 것(§3의 합성통제 또는 실습 1의 역별 데이터), 요금 변화가 없었던 다른 도시의 승차를 대조로 넣는 것이 다음 단계다. 부정적 결과를 이렇게 적는 것이 4장 §2 체크리스트의 ②(정직한 구간)를 지키는 방식이다.

출처 서울특별시, 기후동행카드 시범사업 시행(2024-01-27) 안내, news.seoul.go.kr/traffic/climatecard

5
6·7·11장의 실습 · LaLonde · DoWhy + EconML

DML 과 CATE: ML 을 써도 구간이 무너지지 않는 배관

실험표본에서는 선형회귀·매칭·DML 이 모두 벤치마크 1,794달러 근처에 있고 반박 3종을 통과한다. 대조군을 조사 자료로 바꾸면 ML 도 식별을 구해 주지 못한다.

식별 → 추정

그래프를 먼저 적고, 추정기는 나중에 고른다

한 줄 목표: DoWhy 에 그래프를 명시해 backdoor 식별식을 얻고, 그 식별식 위에서 선형회귀·성향점수 매칭·LinearDML 세 추정치를 나란히 놓는다.

LAB · 코드형

DoWhy 파이프라인 한 블록

공변량 8개가 처치와 결과 모두의 원인이라는 그래프다. 식별 단계를 건너뛰면 estimate_effect 는 실행되지 않는다.

from dowhy import CausalModel from sklearn.ensemble import GradientBoostingRegressor, GradientBoostingClassifier from sklearn.pipeline import make_pipeline; from sklearn.preprocessing import StandardScaler; from sklearn.linear_model import LogisticRegression PS = {"propensity_score_model": make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000))} # 성향점수 모형을 명시(수렴 보장) COV = ["age", "educ", "black", "hisp", "married", "nodegr", "re74", "re75"] graph = "digraph { treat -> re78; " + " ".join(f"{c} -> treat; {c} -> re78;" for c in COV) + " }" la["treat"] = la.treat.astype(int) model = CausalModel(data=la, treatment="treat", outcome="re78", graph=graph) # ① 식별: 그래프를 명시 estimand = model.identify_effect() # backdoor 식별식 출력 e_lr = model.estimate_effect(estimand, method_name="backdoor.linear_regression") # ② 추정 3종 e_psm = model.estimate_effect(estimand, method_name="backdoor.propensity_score_matching", target_units="att", method_params=PS) np.random.seed(0); e_dml = model.estimate_effect(estimand, method_name="backdoor.econml.dml.LinearDML", method_params={"init_params": {"model_y": GradientBoostingRegressor(random_state=0), "model_t": GradientBoostingClassifier(random_state=0), "discrete_treatment": True, "cv": 5, "random_state": 0}, "fit_params": {}}) print(round(e_lr.value), round(e_psm.value), round(e_dml.value)) # → 1676 2708 약 2000 (DML 은 교차적합 겹 분할 난수로 ±30) · 실험표본의 단순 평균 차이 1,794 가 벤치마크

관찰 포인트print(estimand) 의 backdoor 식별식에 공변량 8개가 조건부로 잡혀 있으면 식별 단계 통과. 세 추정치가 모두 1,794 의 표준오차(약 700~900) 안팎에 있으면 추정 단계 통과다. 성향점수 모형을 PS 로 명시하지 않으면 기본 로지스틱 회귀가 소득 변수의 척도 때문에 수렴하지 못해 매칭 결과가 실행마다 달라진다(이 데이터에서 1,377~2,144).

DML 의 직교 스코어를 적어 두면 표의 "SE" 열이 어디서 왔는지 보인다. 7장 §2의 부분선형모형 \(Y = \theta D + f(X) + \varepsilon\) 에서 결과와 처치를 양쪽 다 잔차화해

\[ \hat\theta_{\text{DML}} = \frac{\sum_i \tilde D_i \tilde Y_i}{\sum_i \tilde D_i^{2}},\qquad \tilde Y_i = Y_i - \hat m_{-k(i)}(X_i),\quad \tilde D_i = D_i - \hat p_{-k(i)}(X_i) \]

여기서 \(\hat m_{-k(i)}, \hat p_{-k(i)}\) 는 관측치 \(i\) 가 속하지 않은 겹으로 학습한 결과모형과 성향함수(교차적합)다. 두 nuisance 의 오차가 곱으로만 들어가므로 GradientBoosting 이 \(n^{-1/4}\) 로만 수렴해도 \(\sqrt n\) 신뢰구간이 유효하다.

추정기NSW 실험표본 (n = 445)SE · 95% CINSW 처치군 + PSID 대조군 (n = 2,675)SE · 95% CI
단순 평균 차이1,794(벤치마크)−15,205
선형회귀 (backdoor.linear_regression)1,676677 · [350, 3,003]752783 · [−783, 2,287]
성향점수 매칭 (ATT, 1-NN)2,7082,697
LinearDML (GB, 교차적합 5)1,957734 · [520, 3,395]−570916 · [−2,366, 1,226]

실행 2026-09-12 · dowhy 0.14 · econml 0.17.0 · seed 0 · DML 행은 econml 직접 호출(random_state 0) · DoWhy 래퍼 경유 시 겹 분할 난수로 1,978~2,019 / −569~−654 · 선형회귀 SE 는 HC1 · PSM 은 StandardScaler+LogisticRegression 성향점수의 1-NN

더 읽기

왼쪽 두 열은 무작위 실험이므로 세 추정기가 모두 벤치마크 근처인 것이 정상이고, 이 표는 방법이 아니라 배관을 검증한다. 오른쪽 두 열이 이 회차에서 가장 중요한 표다. 대조군을 실험 대조군 260명에서 PSID 조사 응답자 2,490명으로 바꾸면 단순 차이는 −15,205달러로 뒤집힌다. 훈련 참가자는 애초에 소득이 훨씬 낮은 사람들이기 때문이다. 공변량 8개를 조건부로 잡으면 선형회귀는 752, 1-NN 매칭은 2,697 까지 회복하지만(벤치마크를 넘어선 과대 추정), LinearDML 은 −570 로 부호까지 틀렸고 구간이 벤치마크를 겨우 포함한다.

DML 이 여기서 실패한 이유는 ML 의 결함이 아니라 겹침(overlap) 이다. PSID 대조군의 대부분은 1974·75년 소득이 처치군과 전혀 겹치지 않는 영역에 있어 성향점수가 0 에 붙고, 부분선형모형은 그 영역까지 하나의 \(\theta\) 로 평균한다. 매칭은 처치군 각각에 가장 가까운 대조 관측치만 골라 쓰므로 겹치는 영역 안에서 비교가 이뤄져 방향은 맞지만, 대조 후보 2,490명 중 185명만 쓰는 1-NN 의 잡음이 크다(실험표본에서도 2,708 로 벤치마크보다 900 높다). 7장 §2의 문장을 데이터가 되풀이한다. 직교화는 편향의 차수를 다루는 장치이지 겹침의 해결책이 아니며, 식별 가정을 대체하지 않는다. Dehejia & Wahba (1999)가 성향점수 매칭으로 이 표본에서 벤치마크를 회복했을 때도, 핵심은 방법이 아니라 겹치는 영역으로 표본을 제한한 것이었다.

출처 Sharma & Kiciman, "DoWhy: An End-to-End Library for Causal Inference", arXiv:2011.04216, 2020, arxiv.org/abs/2011.04216 · Chernozhukov 외, "Double/Debiased Machine Learning for Treatment and Structural Parameters", Econometrics Journal 21(1), 2018, doi.org/10.1111/ectj.12097 · Dehejia & Wahba, "Causal Effects in Nonexperimental Studies", JASA 94(448), 1999 · LaLonde, "Evaluating the Econometric Evaluations of Training Programs with Experimental Data", AER 76(4), 1986.

반박

반박 3종: 통과는 증명이 아니라 "탈락하지 않음"

한 줄 목표: 위약 처치·가짜 교란·부분표본 세 검정을 한 루프로 돌려 표 하나로 만든다. 실험표본의 DML 추정치는 셋을 모두 통과했다.

LAB · 코드형

refute 한 블록

DoWhy 의 반박기는 추정 파이프라인 전체를 바꾼 데이터로 재실행한다. 위약 처치는 0 근처여야 하고, 나머지 둘은 원 추정치에서 움직이지 않아야 한다.

refs = {} for name, kw in [("placebo_treatment_refuter", {"placebo_type": "permute"}), # 처치를 무작위로 섞기 → 0 근처여야 통과 ("random_common_cause", {}), # 가짜 교란 추가 → 추정치가 안 흔들려야 ("data_subset_refuter", {"subset_fraction": 0.8})]: # 80% 부분표본 → 안정적이어야 rf = model.refute_estimate(estimand, e_dml, method_name=name, num_simulations=20, random_seed=0, **kw) refs[name] = (round(rf.estimated_effect), round(rf.new_effect), round(rf.refutation_result["p_value"], 2)) print(pd.DataFrame(refs, index=["원 추정치", "새 추정치", "p"]).T) # ③ 반박 # → placebo 1995 → -119 (p .44) · random_cc 1995 → 1820 (p .24) · subset 1995 → 1775 (p .35)

관찰 포인트 — 위약 처치의 새 추정치가 0 근처(−119)이고 p 가 크면(0.44: "새 추정치가 0 과 다르다"는 증거 없음) 통과. 나머지 둘의 p 는 "새 추정치가 원 추정치와 다르다"는 검정이므로 역시 커야 통과다. 열린 과제: 같은 루프를 PSID 버전의 m2 에 돌려 보라. 통과하는가, 그리고 통과가 무엇을 뜻하는가.

반박통과 조건NSW: 원 → 새 추정치 (p)판정PSID: 원 → 새 (p)판정
placebo treatment (처치 무작위 치환)새 추정치 ≈ 01,995 → −119 (0.44)통과−569 → 299 (0.37)통과
random common cause (가짜 교란 추가)새 추정치 ≈ 원 추정치1,995 → 1,820 (0.24)통과−569 → −655 (0.34)통과
data subset (80% 부분표본)새 추정치 ≈ 원 추정치1,995 → 1,775 (0.35)통과−569 → −1,120 (0.19)통과

num_simulations 30 · random_seed 0 · 반박 대상은 DoWhy 래퍼의 DML 추정치(1,995 / −569) · 노트북은 20회

널리 퍼진 오해

"반박 검정을 통과했으니 인과가 확인됐다." — 오른쪽 열이 반례다. PSID 버전의 −569 는 부호부터 틀린 추정치인데 세 반박을 모두 통과한다. 세 검정은 관측되지 않은 교란과 겹침 실패를 건드리지 못한다. 위약 처치는 "파이프라인이 처치와 무관한 구조를 효과로 읽는가"를, 가짜 교란은 "조정이 잡음에 취약한가"를, 부분표본은 "결론이 특정 관측치에 의존하는가"를 찌를 뿐이다. 통과는 "이 세 방향으로는 틀리지 않았다"는 뜻이고, 그 이상은 식별 가정의 몫이다.

출처 DoWhy 문서, 반박 검정 목록, pywhy.org/dowhy · 인과추론 11장 §2 최소 워크플로

이질적 효과 · 하위집단

CATE 분포와 "효과가 큰 하위집단"의 프로파일 표

CausalForestDML 의 개인별 효과는 평균 1,948달러, 표준편차 1,051달러로 퍼져 있고, 공변량 K-means(k = 4)로 나누면 고졸 이상 88명의 군집만 90% 구간이 0 을 제외한다(3,039달러). 나머지 세 군집은 점추정은 양수지만 구간이 0 을 걸친다.

왼쪽: CausalForestDML 개인별 CATE 히스토그램. 오른쪽: 공변량 K-means 4개 군집의 평균 CATE 와 90% 구간
그림 6. 왼쪽 히스토그램의 폭(5% 분위 83달러 ~ 95% 분위 3,778달러)은 효과의 이질성과 추정 잡음이 섞인 것이라 그대로 "효과가 이렇게 다르다"로 읽으면 안 된다. 개인별 90% 구간이 0 을 제외하는 사람은 49% 다. 오른쪽처럼 사전에 정한 공변량 군집으로 묶어 군집 평균의 구간을 보는 것이 6장이 권한 보고 방식이며, 군집 1(브릭)만 구간이 0 을 제외한다.
군집 (n)나이교육연수흑인히스패닉기혼무학위1974 소득1975 소득평균 CATE90% 구간
1 · 고졸 이상 (88)26.812.40.830.000.150.001,6657693,039[906, 5,172]
0 · 고소득 이력·기혼 (30)27.910.20.930.000.570.8317,0899,4051,986[−659, 4,631]
2 · 무학위·저소득 (288)25.09.60.940.000.131.007416701,684[−171, 3,539]
3 · 히스패닉 (39)23.39.40.001.000.180.901,6101,7921,411[−536, 3,358]

CausalForestDML(n_estimators 1000, min_samples_leaf 10, cv 5, nuisance GradientBoosting) · K-means n_init 20 · 군집 구간은 econml ate_inference(X[군집]) · 군집 이름은 공변량 평균을 보고 붙인 것

더 읽기

이 표를 논문에 쓸 때의 규칙 세 가지. 첫째, 군집은 공변량으로만 만든다. CATE 추정치로 군집을 만들면 "효과가 큰 집단"은 잡음이 큰 쪽으로 편향된 순환 논증이 된다. 둘째, 군집 수와 방법(k = 4, K-means)은 결과를 보기 전에 정하거나 부록에 민감도(k = 3, 5)를 둔다. 셋째, 군집 이름은 공변량 평균의 서술이지 인과적 해석이 아니다. "고졸 이상에게 효과가 크다"는 이 표가 말하는 전부이고, "왜"는 다른 증거가 필요하다.

n = 445 에서 CATE 를 추정하는 것은 사실 무리한 일이고, 그것이 개인별 구간의 절반이 0 을 걸치는 이유다. 6장이 IHDP 같은 반합성 벤치마크에 주의하라고 한 것과 같은 맥락에서, 이 표는 "CATE 배관이 돌아간다"를 보이는 것이지 LaLonde 의 이질성에 대한 새로운 주장이 아니다. 본인 데이터가 수만 명 규모라면 같은 코드가 실질적인 표가 된다.

출처 Athey & Wager, "Estimating Treatment Effects with Causal Forests: An Application", Observational Studies 5, 2019, arxiv.org/abs/1902.07409 · EconML 저장소, github.com/py-why/EconML · 인과추론 6장 메타러너와 인과 숲

6
11장 체크리스트 · 보고 순서 · 자기 데이터

논문에 쓰기: 체크리스트를 결과표로

11장의 다섯 항목은 그대로 결과표의 다섯 열이 된다. 어떤 방법을 쓰든 표의 모양이 같으면 심사자가 읽는 순서도 같아진다.

본문 표

네 분석을 한 표에: 식별 가정 → 정황 증거 → 추정치·불확실성 → 반박·기준선

아래 표는 이 회차의 네 분석을 11장 체크리스트의 열로 정리한 것이다. 노트북의 마지막 셀이 같은 표를 코드로 만든다.

분석식별 가정 (11장 ①)정황 증거 (②)추정치 · 불확실성 (③)반박 · 기준선 (④)한계 (⑤)
DiD · organ평행추세 · 무예견event study 사전 계수 −0.003, +0.006 (하나 유의)−0.0225 (클러스터 SE 0.0061, 27개 주)TWFE 동일 · 클러스터 없는 SE 와 대비사전 시점 3개 · 처치 클러스터 1개
합성통제 · Prop 99볼록결합 안의 사전 적합사전 RMSPE 1.66갑, 가중치 6개 주사후 평균 −19.5갑, 2000년 −26.6갑in-space 위약 3/39 (p 0.077), 2× 제외 1/29 (p 0.034)예측변수 미사용 · in-time 위약 미실시
반사실 예측 · 지하철개입 전 관계의 유지 · 학습은 개입 전만위약 연도 누적 잔차 +1.4%2020년 −797백만 명 [−804, −706]릿지 기준선 −741 · 기후동행카드는 검출 불가위약 연도 1개 · 대조 시계열 없음
DML · LaLonde무교란 (backdoor, 공변량 8)estimand 출력 · 겹침(PSID 에서 실패)DML 1,957 (SE 734) · OLS 1,676 · PSM 2,708반박 3종 통과 · 벤치마크 1,794n 445 · CATE 구간 절반이 0 포함
보고 순서① 설계와 식별 가정 한 문단 → ② 정황 증거 그림(event study · 사전 적합 · 위약 잔차 · estimand) → ③ 주 추정치 표(불확실성 열 필수) → ④ 반박·기준선 표 → ⑤ 한계 절. 성능표부터 시작하는 순서는 심사에서 뒤집힌다.
부록 재현성 표데이터 버전과 URL · 패키지 버전(dowhy 0.14, econml 0.17.0, statsmodels 0.15.0, torch 2.11) · seed(0; MLP 0–4) · 실행 환경(RTX 4090, 2026-09-12) · 코드 저장소. 이 페이지의 "실행" 줄들이 그 초안이다.
더 읽기

표의 "한계" 열은 방법의 결함이 아니라 데이터가 허락하지 않은 것을 적는 자리다. 사전 시점이 셋뿐이면 사전 추세 검정의 힘이 약하고, 위약 연도가 하나면 구간이 거칠고, n 이 445 면 CATE 는 배관 시연에 그친다. 이렇게 적힌 한계는 다음 연구의 설계 목록이 된다. 반대로 한계 열이 비어 있는 표는 심사자가 채워 넣게 되고, 그때는 저자가 고른 단어로 채워지지 않는다.

출처 인과추론 11장 §3 논문 작성 공통 체크리스트 · Roth, Sant'Anna, Bilinski & Poe, "What's Trending in Difference-in-Differences?", Journal of Econometrics 235(2), 2023, arxiv.org/abs/2201.01194

자기 데이터로

노트북의 ① 블록만 바꾼다

노트북은 데이터 로드 셀(②)의 네 변수 od, p99, sub, la 만 바꾸면 나머지 셀이 그대로 돌아가도록 짜여 있다. 본인 데이터의 모양을 아래 표에서 찾아 그 행의 셀을 바꾼다.

내 데이터의 모양바꿀 것바꾸지 말 것이 회차의 절
여러 단위 × 몇 시점 패널, 일부 단위만 처치od → 내 패널(단위, 시점, 결과). Treat·Post 정의, event study 기준 시점클러스터 = 처치가 배정된 단위. 사전 계수 표를 그대로 싣기§2
처치 단위 1개 + 대조 단위 여러 개, 긴 시계열p99 → (시점, 단위, 결과) 긴 형식. 처치 시점 pre비음·합 1 제약. 위약 제외 기준은 사전 RMSPE 로만§3
처치 단위 1개의 긴 일별·주별 시계열, 대조 없음sub → (date, 결과). 개입일. feats 의 캘린더 특징(주간 데이터면 요일 원핫 제거)학습은 개입 전만(정규화·조기종료 포함). 위약 시점 검정 → 구간§4
개인·기업 횡단면, 처치 더미 + 공변량la → 내 표. COVgraph(어느 변수가 처치·결과의 공통원인인지)식별 → 추정 → 반박 순서. 겹침 진단(성향점수 분포)을 추가§5
인과추론 시리즈와의 연결

§2 는 1장·2장(DiD 와 평행추세), 처치 시점이 단위마다 다르면 3장. §3·§4 는 4장(합성통제·CausalImpact·등각예측). §5 는 5장~7장(무교란·메타러너·DML). 절차 전체는 11장.

다음 회차(실습 5)는 이 표의 한 행을 본인 데이터로 실제로 바꾸는 워크숍이다. 데이터를 가져오기 전에 "처치 단위·처치 시점·결과변수" 세 가지를 한 줄로 적어 오는 것이 준비물이다.

7
이해 점검

퀴즈와 핵심 정리

먼저 스스로 답한 뒤 펼쳐서 확인한다.

이해 점검

퀴즈

organ_donations 회귀에서 iid 표준오차(0.125)와 주 클러스터 표준오차(0.006)가 스무 배 차이 났다. 보통은 클러스터링이 표준오차를 키운다고 배우는데, 왜 여기서는 줄었는가?
주 고정효과 없이 Treat*Post 만 넣은 모형이라 주 간 수준 차이(0.26~0.75)가 전부 오차항에 들어갔고, iid 공식은 그 분산을 상호작용항의 불확실성으로 계산한다. 클러스터 SE 는 같은 주의 관측치를 하나로 묶어 주 간 수준 차이가 상호작용 계수의 표본 변동에 기여하지 않게 한다. 주·분기 고정효과를 넣으면(표의 마지막 행) 두 SE 의 차이가 사라지므로, 이 현상은 클러스터링의 성질이 아니라 모형 설정의 결과다. 1장이 말한 "클러스터링은 SE 를 키운다"는 계열상관이 있는 통상적 상황의 이야기다.
합성통제 위약검정에서 미주리는 사후 평균 격차가 +9갑뿐인데 비율 순위가 1위였고, 캘리포니아(−19.5갑)는 3위였다. 비율 검정과 격차 검정 중 어느 쪽을 논문에 써야 하는가?
둘 다 쓴다. 비율 검정은 사전 적합이 매우 좋은 주(미주리 사전 RMSPE 0.44갑)의 작은 사후 격차를 과대평가하고, 격차 검정은 사전 적합이 나쁜 주(뉴햄프셔 58.6갑)의 큰 사후 격차를 효과처럼 취급한다. 원 논문의 방식은 격차 검정에 사전 RMSPE 제외 기준(20×, 5×, 2×)을 두어 후자를 막는 것이고, 비율 검정은 제외 없이 한 숫자로 요약하는 대안이다. 표에 네 p 값(0.077, 0.079, 0.057, 0.034)을 모두 적고 "제외 기준에 따라 이 범위에서 움직인다"고 쓰는 것이 정직한 보고다. 어느 하나만 고르면 고른 이유를 의심받는다.
PSID 관측 버전에서 LinearDML 은 −570달러를 냈고 반박 3종을 모두 통과했다. 이 추정치를 논문에 "반박 검정을 통과한 ML 기반 추정치"로 보고해도 되는가?
안 된다. 실험 벤치마크 1,794 와 부호가 다르고, 실패 원인은 세 반박이 건드리지 못하는 곳(겹침)에 있다. PSID 대조군 대부분은 처치군과 1974·75년 소득이 겹치지 않는 영역에 있어 성향점수가 0 에 붙고, 부분선형모형은 그 영역까지 하나의 계수로 평균한다. 위약 처치·가짜 교란·부분표본은 이 문제에 둔감하다. 보고해야 할 것은 성향점수 분포의 겹침 진단(5장)과, 겹치는 영역으로 표본을 제한한 뒤의 재추정이다. 반박 통과는 "세 방향으로는 틀리지 않았다"는 진술이지 식별의 증명이 아니다.
핵심 정리

하나. 반사실을 만드는 재료는 셋이다. 대조군의 평균(DiD), 대조 단위의 가중조합(합성통제), 개입 전 데이터로 학습한 예측(딥러닝 반사실). 어느 재료든 "개입 후 처치 단위의 정보"가 반사실에 섞이는 순간 효과는 0 쪽으로 눌린다.

둘. 불확실성의 도구도 재료를 따라간다. 평균에는 클러스터 SE, 가중조합에는 위약 순열, 예측에는 위약 시점의 잔차로 만든 등각 구간. 점추정만 있는 반사실은 인과 주장이 아니다. 기후동행카드의 "검출 불가"가 그 구간이 하는 일이다.

셋. ML 은 nuisance 를 맡을 뿐 식별을 대신하지 않는다. 실험표본에서 DML 은 벤치마크를 회복하고 반박을 통과하지만, 겹침이 무너진 관측표본에서는 부호가 틀린 채로 같은 반박을 통과한다. 식별 → 추정 → 반박의 순서를 코드가 강제하더라도, 첫 단계는 여전히 연구자의 문장이다.

8
원문 링크

참고문헌

연도는 출간 기준. 수치는 본문에 인용한 원문 또는 이 페이지의 실행 결과 기준이다.

데이터 출처

  1. Huntington-Klein, N. (2021). The Effect: An Introduction to Research Design and Causality. Chapman & Hall/CRC. theeffectbook.net — organ_donations 예제 (causaldata 패키지).
  2. Kessler, J. B., & Roth, A. E. (2014). Don't Take 'No' for an Answer: An Experiment with Actual Organ Donor Registrations. NBER Working Paper 20378. nber.org/papers/w20378
  3. Arkhangelsky, D., Athey, S., Hirshberg, D. A., Imbens, G. W., & Wager, S. (2021). Synthetic Difference-in-Differences. American Economic Review, 111(12), 4088–4118. doi.org/10.1257/aer.20190159 — synthdid 저장소의 california_prop99 데이터.
  4. LaLonde, R. J. (1986). Evaluating the Econometric Evaluations of Training Programs with Experimental Data. American Economic Review, 76(4), 604–620.
  5. Dehejia, R. H., & Wahba, S. (1999). Causal Effects in Nonexperimental Studies: Reevaluating the Evaluation of Training Programs. JASA, 94(448), 1053–1062. 데이터: users.nber.org/~rdehejia/nswdata2.html
  6. 서울열린데이터광장. 서울시 지하철 호선별 역별 승하차 인원 정보 (OA-12914). data.seoul.go.kr

방법 논문

  1. Abadie, A., Diamond, A., & Hainmueller, J. (2010). Synthetic Control Methods for Comparative Case Studies: Estimating the Effect of California's Tobacco Control Program. JASA, 105(490), 493–505. doi.org/10.1198/jasa.2009.ap08746
  2. Brodersen, K. H., Gallusser, F., Koehler, J., Remy, N., & Scott, S. L. (2015). Inferring Causal Impact Using Bayesian Structural Time-Series Models. Annals of Applied Statistics, 9(1), 247–274. doi.org/10.1214/14-AOAS788
  3. Chernozhukov, V., Chetverikov, D., Demirer, M., Duflo, E., Hansen, C., Newey, W., & Robins, J. (2018). Double/Debiased Machine Learning for Treatment and Structural Parameters. Econometrics Journal, 21(1), C1–C68. doi.org/10.1111/ectj.12097
  4. Athey, S., & Wager, S. (2019). Estimating Treatment Effects with Causal Forests: An Application. Observational Studies, 5(2), 37–51. arxiv.org/abs/1902.07409
  5. Chernozhukov, V., Wüthrich, K., & Zhu, Y. (2021). An Exact and Robust Conformal Inference Method for Counterfactual and Synthetic Controls. JASA, 116(536), 1849–1864. arxiv.org/abs/1712.09089
  6. Bertrand, M., Duflo, E., & Mullainathan, S. (2004). How Much Should We Trust Differences-in-Differences Estimates? Quarterly Journal of Economics, 119(1), 249–275.

도구

  1. Sharma, A., & Kiciman, E. (2020). DoWhy: An End-to-End Library for Causal Inference. arXiv:2011.04216. arxiv.org/abs/2011.04216 · 문서 pywhy.org/dowhy
  2. EconML. Microsoft Research / PyWhy. github.com/py-why/EconML
  3. synthdid. Arkhangelsky 외. github.com/synth-inference/synthdid

인용 원칙 표의 수치는 2026-09-12 에 40_causal_experiment.py 를 실행한 값이다(seed 0, MLP seed 0–4, RTX 4090, dowhy 0.14, econml 0.17.0, statsmodels 0.15.0, torch 2.11.0, scipy 1.16.3). 원 논문의 수치는 해당 논문 본문 기준이며, 이 페이지의 실행값과 다른 곳은 본문에 차이의 이유를 적었다.

한국외국어대학교 Global Business & Technology · 대학원 딥러닝 세미나 · 실데이터 실습 4.
이 자료는 단일 HTML 파일로 배포되며, 수식은 MathJax, 위약 순열 뷰어는 순수 JavaScript 로 구현되어 오프라인에서도 동작한다(수식 렌더링만 최초 1회 네트워크 필요). 색상 규약: 붉은색 = 처치군, 청록색 = 대조군, 회색 점선 = 반사실.