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) != 0

Animation 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] != 0

02 / 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 X

05 / 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)]^2

03 / 代码

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 / 风险

常见误区

工具变量只强相关但不外生,或排除限制无法用制度机制解释。
第一阶段过弱却仍把 2SLS 系数当成精确因果效应。
RD 阈值附近存在操纵、排序或带宽选择敏感,却只报告一个漂亮断点图。

参考资料