Causal Identification / IV + RD
IV 工具变量与 RD 断点回归
IV 用外生冲击隔离处理变量中可信的变化,RD 用阈值附近的局部连续性构造准随机比较;两者都在回答同一个问题:反事实从哪里来?
Mechanism Lab
动画:IV 如何隔离外生变化,RD 如何读取阈值跳跃
动画左侧展示工具变量 Z 先推动处理 D,再影响结果 Y;右侧展示 running variable 到达阈值后处理概率和结果的局部跳跃。
Step 1 / 5
Instrument
工具变量必须先产生第一阶段:Z 改变处理概率或处理强度。
Cov(Z,D) != 0Animation Control
Reduced-motion users receive the same step states without continuous motion.
01 / 直觉
核心直觉
IV 适合处理变量 D 内生时:我们不用 D 的全部变化,只用由工具 Z 推动的那部分变化。
RD 适合存在明确阈值 c 时:阈值两侧非常接近的个体应当相似,处理概率或处理状态在阈值处跳变。
两者都不是“跑一个特殊回归”就成立;关键是工具变量假设、断点连续性、第一阶段强度和局部样本选择是否可信。
02 / 数学
IV 的 LATE 与 RD 的局部跳跃
01 / IV 目标与第一阶段
令 Z 为工具,D 为处理,Y 为结果。工具变量首先必须影响处理变量;没有第一阶段,就没有可识别的处理变化。
First stage: E[D|Z=1] - E[D|Z=0] != 002 / Wald / LATE
在独立性、排除限制和单调性下,Z 对 Y 的 reduced form 跳跃除以 Z 对 D 的第一阶段跳跃,识别的是遵从者的局部平均处理效应。
tau_LATE = {E[Y|Z=1] - E[Y|Z=0]} / {E[D|Z=1] - E[D|Z=0]}03 / 协方差形式
在线性单工具情形,Wald 估计量也可写成协方差比值。它只使用由 Z 解释的 D 的变化。
beta_IV = Cov(Z,Y) / Cov(Z,D)04 / 2SLS 推导
两阶段最小二乘先把 D 投影到 Z 和控制变量上,得到 D_hat;再用 D_hat 解释 Y。矩阵形式中 P_Z 是工具变量空间的投影矩阵。
D_hat = P_Z D
beta_2SLS = (X_hat^T X)^(-1) X_hat^T Y, X_hat = P_Z X05 / Sharp RD
若阈值 c 完全决定处理 D=1[X>=c],则阈值右侧和左侧条件期望的极限差识别断点处的局部处理效应。
tau_RD = lim_{x down c} E[Y|X=x] - lim_{x up c} E[Y|X=x]06 / Fuzzy RD
若阈值只改变处理概率而不完全决定处理,RD 变成局部 Wald:结果在阈值处的跳跃除以处理概率的跳跃。
tau_FRD = jump_Y(c) / jump_D(c)07 / 局部线性估计
实际估计常在阈值附近带宽 h 内做局部线性回归,并用三角核给离阈值更近的样本更高权重。
min sum_i K((X_i-c)/h) [Y_i - alpha - tau 1{X_i>=c} - beta_l(X_i-c) - beta_r 1{X_i>=c}(X_i-c)]^203 / 代码
Python 代码:IV 2SLS 与 RD 局部线性估计
IV 部分用 linearmodels 写 2SLS;RD 部分用 statsmodels 手写三角核局部线性回归。真实论文还要报告弱工具检验、带宽敏感性和断点操纵诊断。
import numpy as np
import pandas as pd
import statsmodels.api as sm
from linearmodels.iv import IV2SLS
# IV example:
# outcome: test_score
# treatment: program_enroll
# instrument: lottery_offer
# controls: baseline_score, age, income
iv_formula = (
"test_score ~ 1 + baseline_score + age + income "
"+ [program_enroll ~ lottery_offer]"
)
iv_model = IV2SLS.from_formula(iv_formula, data=df).fit(
cov_type="clustered",
clusters=df["school_id"],
)
print(iv_model.summary)
# RD example:
# running variable: assignment_score
# cutoff: 70
# outcome: test_score
def triangular_kernel(u):
return np.maximum(1 - np.abs(u), 0)
def local_linear_rd(data, outcome, running, cutoff, bandwidth):
sample = data[np.abs(data[running] - cutoff) <= bandwidth].copy()
sample["right"] = (sample[running] >= cutoff).astype(int)
sample["centered"] = sample[running] - cutoff
sample["right_x_centered"] = sample["right"] * sample["centered"]
weights = triangular_kernel(sample["centered"] / bandwidth)
X = sm.add_constant(sample[["right", "centered", "right_x_centered"]])
fit = sm.WLS(sample[outcome], X, weights=weights).fit(cov_type="HC1")
return fit.params["right"], fit.conf_int().loc["right"], fit
tau, ci, rd_fit = local_linear_rd(
df,
outcome="test_score",
running="assignment_score",
cutoff=70,
bandwidth=8,
)
print({"rd_effect": tau, "ci_low": ci[0], "ci_high": ci[1]})04 / 案例
案例:抽签名额与分数阈值的教育项目评估
- IV 场景:学校项目名额有限,用抽签 offer 作为工具变量。offer 影响是否参加项目,但在假设成立时不应直接影响成绩,除非通过参加项目。
- IV 的报告重点是 reduced form、第一阶段、2SLS/LATE、弱工具风险、排除限制和遵从者解释。
- RD 场景:奖学金资格由 70 分阈值决定。阈值附近 69.8 与 70.2 的学生应当相似,资格跳变提供局部因果比较。
- RD 的报告重点是阈值两侧图形、局部线性估计、带宽敏感性、协变量连续性、running variable 密度是否在阈值处异常跳变。
05 / 风险
常见误区
参考资料
- Angrist, Imbens, and Rubin (1996), Identification of Causal Effects Using Instrumental Variableshttps://doi.org/10.1080/01621459.1996.10476902
- Imbens and Lemieux (2008), Regression Discontinuity Designshttps://doi.org/10.1016/j.jeconom.2007.05.001
- Lee and Lemieux (2010), Regression Discontinuity Designs in Economicshttps://doi.org/10.1257/jel.48.2.281