Skip to content

11.2 RDD 原理与识别策略

"Regression discontinuity is one of the most credible quasi-experimental strategies.""断点回归是最可信的准实验策略之一。"— Guido Imbens & Thomas Lemieux, RDD Review Authors (RDD综述作者)

从潜在结果框架到局部随机化:RDD 的理论基础


本节概要

在本节中,我们将深入探讨:

  • Sharp RDD 的严格数学推导
  • Fuzzy RDD 与工具变量(IV)的关系
  • 局部平均处理效应(LATE)的含义
  • 参数 vs 非参数估计方法
  • 带宽选择的权衡

Sharp RDD 的识别理论

潜在结果框架的回顾

Rubin 因果模型中,每个个体 都有两个潜在结果:

  • :如果接受处理
  • :如果不接受处理

个体处理效应

根本性问题:我们永远无法同时观察到

观察到的结果

其中 是处理指示变量。

RDD 的识别逻辑

核心想法:在断点 附近,处理分配"准随机"。

Sharp RDD 的分配规则

关键假设:连续性假设(Continuity Assumption)

对于 ,假设:

直觉

  • 在断点 处,潜在结果函数 是连续的
  • 换句话说,驱动变量 的微小变化不会导致潜在结果的跳跃

识别策略的推导

观察到的条件期望

在断点右侧(,所有人都接受处理):

在断点左侧(,所有人都不接受处理):

RDD 估计量

为什么这是因果效应?

根据连续性假设:

关键点

  1. RDD 估计的是断点处的处理效应,而非总体ATE
  2. 这是一个局部效应(Local Average Treatment Effect, LATE)
  3. 外推到其他 值需要额外的假设(如效应同质性)

Fuzzy RDD:不完美的断点规则

Fuzzy RDD 的动机

在现实中,处理分配规则可能不完美

例子 1:大学录取

  • 规则:高考分数 ≥ 600 → 录取
  • 现实:有特殊录取(体育特长、艺术生、校长推荐等)
  • 结果:

例子 2:医疗保险(Medicare)

  • 规则:年龄 ≥ 65 → 自动获得Medicare
  • 现实:有些人提前购买,有些人选择不参加
  • 结果:处理在断点处跳跃,但不是从0到1

Fuzzy RDD 的定义

Sharp RDD

Fuzzy RDD

但不一定是完美的0和1。

关键条件

即:处理概率在断点处有跳跃

Fuzzy RDD 的识别:工具变量方法

核心思想:将断点指示变量作为工具变量(IV)!

定义工具变量

IV 的三个关键条件

  1. 相关性(Relevance) 与处理 相关

  2. 排他性(Exclusion) 只通过 影响 (控制 后)

  3. 单调性(Monotonicity):跨过断点不会使任何人从处理变为不处理

Fuzzy RDD 估计量

简化形式(Reduced Form)

第一阶段(First Stage)

Fuzzy RDD 效应(Wald 估计量)

Fuzzy RDD 的因果解释

局部平均处理效应(LATE)

Fuzzy RDD 估计的是顺从者(Compliers)的处理效应。

四类个体(基于潜在处理状态):

  1. Always-takers(无论是否跨断点都接受处理)
  2. Never-takers(无论是否跨断点都不接受处理)
  3. Compliers(只有跨过断点才接受处理)
  4. Defiers(跨过断点反而不接受处理)

单调性假设排除了 Defiers 的存在。

LATE 定理(Imbens & Angrist 1994):

解释

  • Fuzzy RDD 估计的是顺从者(那些因为跨过断点而改变处理状态的人)的平均处理效应
  • 对于 Always-takers 和 Never-takers,我们无法识别他们的处理效应
  • 如果 Compliers 与总体不同,LATE ≠ ATE

参数 vs 非参数估计方法

方法 1:全局多项式回归

模型 阶多项式):

优点

  • 简单易懂
  • 使用所有数据,效率高

缺点

  • 对函数形式假设敏感(选择 很关键)
  • 远离断点的数据会影响估计(可能引入偏差)
  • Gelman & Imbens (2019) 警告:不要使用 的多项式

Python 实现

python
import statsmodels.formula.api as smf

# 二阶多项式
model = smf.ols('Y ~ D + X_c + I(X_c**2) + D:X_c + D:I(X_c**2)', data=df).fit()
print(model.summary())

方法 2:局部线性回归(推荐)

思路:只使用断点附近 的数据,拟合线性模型。

模型

带宽(Bandwidth)

  • 太小:方差大(数据少)
  • 太大:偏差大(函数形式假设可能错误)

优点

  • 对函数形式假设最弱(只需局部线性)
  • 理论上最优(Fan & Gijbels 1996)
  • 现代软件(如 rdrobust)自动选择最优带宽

Python 实现

python
# 手动选择带宽
h = 10
df_local = df[np.abs(df['X_c']) <= h]
model_local = smf.ols('Y ~ D + X_c + D:X_c', data=df_local).fit()

# 使用 rdrobust(自动带宽)
from rdrobust import rdrobust
result = rdrobust(y=df['Y'], x=df['X'], c=cutoff)
print(result)

方法 3:核加权局部线性回归

进一步改进:给离断点更近的观测更大的权重。

三角核函数(Triangular Kernel):

权重

加权回归

Python 实现

python
from statsmodels.regression.linear_model import WLS

# 计算权重
df['weight'] = np.maximum(0, 1 - np.abs(df['X_c']) / h)

# 加权最小二乘
model_wls = smf.wls('Y ~ D + X_c + D:X_c', data=df, weights=df['weight']).fit()
print(model_wls.summary())

️ 带宽选择的权衡

偏差-方差权衡(Bias-Variance Tradeoff)

小带宽

  • 优点:偏差小(函数近似更准确)
  • 缺点:方差大(样本量少,估计不稳定)

大带宽

  • 优点:方差小(样本量大,估计稳定)
  • 缺点:偏差大(可能违反局部线性假设)

均方误差(MSE)

最优带宽 :最小化 MSE。

最优带宽选择方法

1. Imbens-Kalyanaraman (IK) 方法(2012)

思路:基于均方误差(MSE)的渐近展开,推导最优带宽。

公式(简化版):

其中:

  • :残差方差
  • :驱动变量在断点处的密度
  • :左侧潜在结果函数的二阶导数

Python 实现

python
from rdrobust import rdbwselect

# 自动选择 IK 带宽
bw = rdbwselect(y=df['Y'], x=df['X'], c=cutoff, bwselect='mserd')
print(f"IK Bandwidth: {bw.bws[0]}")

2. Calonico-Cattaneo-Titiunik (CCT) 方法(2014)

改进

  • 考虑有限样本的偏差校正
  • 提供稳健的置信区间

两种带宽

  1. 主带宽(Main Bandwidth):用于估计点估计
  2. 偏差带宽(Bias Bandwidth):用于估计和校正偏差

Python 实现rdrobust 默认使用 CCT):

python
from rdrobust import rdrobust

# CCT 方法(默认)
result_cct = rdrobust(y=df['Y'], x=df['X'], c=cutoff)
print(result_cct)

交叉验证(Cross-Validation)

留一法交叉验证

  1. 对每个观测 ,移除它
  2. 用剩余数据拟合模型(使用带宽
  3. 预测
  4. 计算预测误差:
  5. 重复所有观测,选择最小化

注意:只使用断点一侧的数据进行交叉验证(避免使用跳跃本身)。


Python 完整示例:Sharp vs Fuzzy RDD

示例 1:Sharp RDD

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.formula.api as smf
from rdrobust import rdrobust, rdplot

# 设置
np.random.seed(42)
n = 2000
c = 0

# 生成驱动变量
X = np.random.normal(0, 10, n)

# Sharp RDD:处理完全由断点决定
D = (X >= c).astype(int)

# 生成结果变量
# DGP: Y = 50 + 0.5*X + 0.01*X^2 + 10*D + noise
true_effect = 10
Y = 50 + 0.5 * X + 0.01 * X**2 + true_effect * D + np.random.normal(0, 5, n)

df = pd.DataFrame({'X': X, 'D': D, 'Y': Y, 'X_c': X - c})

# 估计(使用 rdrobust)
result_sharp = rdrobust(y=df['Y'], x=df['X'], c=c)
print("=" * 70)
print("Sharp RDD 结果")
print("=" * 70)
print(result_sharp)

# 可视化
rdplot(y=df['Y'], x=df['X'], c=c,
       title='Sharp RDD',
       x_label='Running Variable',
       y_label='Outcome')
plt.show()

示例 2:Fuzzy RDD

python
# Fuzzy RDD:处理不完美
# 跨过断点,处理概率从0.2跳到0.8
np.random.seed(42)

# 潜在处理状态
prob_treat = 0.2 + 0.6 * (X >= c)  # 左侧20%,右侧80%
D_fuzzy = np.random.binomial(1, prob_treat)

# 生成结果变量(真实效应还是10)
Y_fuzzy = 50 + 0.5 * X + 0.01 * X**2 + true_effect * D_fuzzy + np.random.normal(0, 5, n)

df_fuzzy = pd.DataFrame({'X': X, 'D': D_fuzzy, 'Y': Y_fuzzy, 'X_c': X - c})

# Fuzzy RDD 估计(自动检测并使用IV)
result_fuzzy = rdrobust(y=df_fuzzy['Y'], x=df_fuzzy['X'], c=c, fuzzy=df_fuzzy['D'])
print("\n" + "=" * 70)
print("Fuzzy RDD 结果")
print("=" * 70)
print(result_fuzzy)

# 检查第一阶段(First Stage)
print("\n第一阶段检验:")
first_stage = rdrobust(y=df_fuzzy['D'], x=df_fuzzy['X'], c=c)
print(f"处理概率的跳跃: {first_stage.coef[0]:.3f}")
print(f"F-统计量: {first_stage.z[0]**2:.2f}")

示例 3:不同带宽的敏感性分析

python
# 尝试不同带宽
bandwidths = [5, 10, 15, 20, 25]
results = []

for h in bandwidths:
    df_local = df[np.abs(df['X_c']) <= h]
    model = smf.ols('Y ~ D + X_c + D:X_c', data=df_local).fit()

    results.append({
        'bandwidth': h,
        'effect': model.params['D'],
        'se': model.bse['D'],
        'n': len(df_local)
    })

results_df = pd.DataFrame(results)

print("\n" + "=" * 70)
print("带宽敏感性分析")
print("=" * 70)
print(results_df.to_string(index=False))

# 可视化
fig, ax = plt.subplots(figsize=(12, 6))
ax.errorbar(results_df['bandwidth'], results_df['effect'],
            yerr=1.96 * results_df['se'],
            fmt='o-', capsize=5, capthick=2, linewidth=2, markersize=8)
ax.axhline(y=true_effect, color='red', linestyle='--', linewidth=2,
           label=f'True Effect = {true_effect}')
ax.set_xlabel('Bandwidth', fontsize=13, fontweight='bold')
ax.set_ylabel('Estimated RDD Effect', fontsize=13, fontweight='bold')
ax.set_title('Sensitivity to Bandwidth Choice', fontsize=15, fontweight='bold')
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

RDD 的统计推断

标准误的计算

异方差稳健标准误(HC1/HC2):

python
model = smf.ols('Y ~ D + X_c + D:X_c', data=df_local).fit(cov_type='HC2')
print(model.summary())

聚类标准误(如果有聚类结构):

python
# 假设数据在学校层面聚类
model_cluster = smf.ols('Y ~ D + X_c + D:X_c', data=df_local).fit(
    cov_type='cluster', cov_kwds={'groups': df_local['school_id']}
)

置信区间的构建

常规置信区间(基于渐近正态性):

稳健置信区间(CCT 方法,考虑有限样本偏差):

python
from rdrobust import rdrobust

# CCT 稳健置信区间
result = rdrobust(y=df['Y'], x=df['X'], c=c)
print(f"点估计: {result.coef[0]:.3f}")
print(f"稳健95% CI: [{result.ci[0][0]:.3f}, {result.ci[0][1]:.3f}]")

Bootstrap 置信区间

python
from scipy.stats import bootstrap

def rdd_estimator(data, indices):
    """RDD 估计量(用于 bootstrap)"""
    df_boot = data.iloc[indices]
    df_boot_local = df_boot[np.abs(df_boot['X_c']) <= h]
    model = smf.ols('Y ~ D + X_c + D:X_c', data=df_boot_local).fit()
    return model.params['D']

# Bootstrap(1000次重抽样)
n_boot = 1000
boot_estimates = []
for _ in range(n_boot):
    indices = np.random.choice(len(df), len(df), replace=True)
    boot_estimates.append(rdd_estimator(df, indices))

boot_estimates = np.array(boot_estimates)
ci_lower = np.percentile(boot_estimates, 2.5)
ci_upper = np.percentile(boot_estimates, 97.5)

print(f"Bootstrap 95% CI: [{ci_lower:.3f}, {ci_upper:.3f}]")

关键要点

Sharp RDD

  1. 识别条件:连续性假设(潜在结果在断点处连续)
  2. 估计量:断点处观察结果的跳跃
  3. 因果解释:断点处的局部平均处理效应(LATE)
  4. 最佳实践:使用局部线性回归 + 自动带宽选择(CCT)

Fuzzy RDD

  1. 本质:工具变量估计,断点作为IV
  2. 识别条件:连续性 + IV假设(相关性、排他性、单调性)
  3. 估计量:Wald 估计量(结果跳跃 / 处理跳跃)
  4. 因果解释:顺从者(Compliers)的LATE
  5. 检验:第一阶段要足够强(F > 10)

带宽选择

  1. 权衡:偏差(小带宽)vs 方差(大带宽)
  2. 最优方法:IK 或 CCT(自动数据驱动)
  3. 稳健性:报告多个带宽下的结果

本节总结

在本节中,我们学习了:

  • Sharp RDD 的严格数学推导(从潜在结果框架出发)
  • Fuzzy RDD 与工具变量的深刻联系
  • 局部平均处理效应(LATE)的含义和局限性
  • 参数(多项式)vs 非参数(局部线性)方法的权衡
  • 带宽选择的理论和实践(IK, CCT)
  • 完整的Python实现和稳健推断

下一步:在 第3节 中,我们将学习如何检验RDD的核心假设,包括连续性假设、密度检验(McCrary Test)和协变量平衡。


理论扎实,实证才能可信!

基于 MIT 许可证发布。内容版权归作者所有。