Skip to content

11.4 带宽选择与稳健性检验

"Bandwidth choice involves a fundamental bias-variance tradeoff.""带宽选择涉及基本的偏差-方差权衡。"— Guido Imbens & Karthik Kalyanaraman, Optimal Bandwidth Authors (最优带宽作者)

让你的RDD估计经得起审稿人的考验


本节概要

带宽选择是RDD实证分析中最关键的决策之一。本节将介绍:

  • 最优带宽选择的理论与方法(IK, CCT)
  • 敏感性分析(Sensitivity Analysis)
  • 多项式阶数的选择
  • Donut-hole RDD(排除断点附近的观测)
  • 完整的稳健性检验流程

️ 带宽选择的核心权衡

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

均方误差(MSE)

小带宽

  • 偏差小:局部线性近似更准确
  • 方差大:样本量少,估计不稳定

大带宽

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

最优带宽

渐近理论(Asymptotic Theory)

Fan & Gijbels (1996) 推导出最优带宽的渐近形式:

直觉

  • 样本量越大,最优带宽越小(可以用更局部的数据)
  • 带宽以 的速度收缩(比参数估计的 慢)

自动带宽选择方法

方法 1:Imbens-Kalyanaraman (IK) 带宽(2012)

思路:基于MSE的渐近展开,推导数据驱动的最优带宽。

简化公式

其中:

  • :常数(取决于核函数)
  • :断点左右两侧的残差方差
  • :驱动变量在断点处的密度
  • :潜在结果函数的二阶导数

Python 实现

python
from rdrobust import rdbwselect

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

方法 2:Calonico-Cattaneo-Titiunik (CCT) 带宽(2014)

改进

  1. 偏差校正:考虑有限样本的偏差
  2. 两个带宽
    • 主带宽 :用于点估计
    • 偏差带宽 :用于估计和校正偏差

覆盖误差最优(Coverage Error Optimal, CEO)

Python 实现(rdrobust 默认):

python
from rdrobust import rdrobust

# CCT 方法(默认)
result_cct = rdrobust(y=df['Y'], x=df['X'], c=0)
print(f"CCT Main Bandwidth: {result_cct.bws[0]:.2f}")
print(f"CCT Bias Bandwidth: {result_cct.bws[1]:.2f}")

方法 3:交叉验证(Cross-Validation, CV)

留一法交叉验证

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

注意:只使用断点一侧的数据(避免利用跳跃本身)。

python
def cross_validation_bandwidth(df, X_col, Y_col, cutoff, h_candidates):
    """
    交叉验证选择带宽(简化版)

    参数:
    - df: 数据框
    - X_col, Y_col: 变量名
    - cutoff: 断点
    - h_candidates: 候选带宽列表
    """
    cv_scores = []

    for h in h_candidates:
        # 限制样本
        df_local = df[np.abs(df[X_col] - cutoff) <= h].copy()
        df_local['X_c'] = df_local[X_col] - cutoff
        df_local['D'] = (df_local[X_col] >= cutoff).astype(int)

        # 留一法CV(简化:用K-fold代替)
        from sklearn.model_selection import KFold
        kf = KFold(n_splits=5, shuffle=True, random_state=42)

        fold_mse = []
        for train_idx, test_idx in kf.split(df_local):
            df_train = df_local.iloc[train_idx]
            df_test = df_local.iloc[test_idx]

            # 拟合
            model = smf.ols(f'{Y_col} ~ D + X_c + D:X_c', data=df_train).fit()

            # 预测
            y_pred = model.predict(df_test)
            mse = np.mean((df_test[Y_col] - y_pred) ** 2)
            fold_mse.append(mse)

        cv_scores.append(np.mean(fold_mse))

    optimal_h = h_candidates[np.argmin(cv_scores)]
    return optimal_h, cv_scores

# 示例
h_candidates = np.arange(5, 30, 2)
optimal_h, cv_scores = cross_validation_bandwidth(df, 'X', 'Y', 0, h_candidates)
print(f"CV Optimal Bandwidth: {optimal_h:.2f}")

稳健性检验:敏感性分析

1. 多个带宽的比较

最佳实践:报告多个带宽下的估计结果。

python
import numpy as np
import pandas as pd
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt

# 尝试一系列带宽
bandwidths = [5, 10, 15, 20, 25, 30]
results = []

for h in bandwidths:
    df_local = df[np.abs(df['X']) <= h].copy()
    df_local['X_c'] = df_local['X']
    df_local['D'] = (df_local['X'] >= 0).astype(int)

    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'],
        'ci_lower': model.conf_int().loc['D', 0],
        'ci_upper': model.conf_int().loc['D', 1],
        'n_obs': len(df_local)
    })

results_df = pd.DataFrame(results)

# 可视化
fig, ax = plt.subplots(figsize=(12, 7))

ax.plot(results_df['bandwidth'], results_df['effect'],
        'o-', linewidth=2, markersize=8, label='Point Estimate')

ax.fill_between(results_df['bandwidth'],
                results_df['ci_lower'],
                results_df['ci_upper'],
                alpha=0.3, label='95% CI')

ax.axhline(y=0, color='black', linestyle='--', linewidth=1)
ax.set_xlabel('Bandwidth', fontsize=13, fontweight='bold')
ax.set_ylabel('RDD Effect Estimate', 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()

# 打印表格
print("=" * 80)
print("带宽敏感性分析")
print("=" * 80)
print(results_df.to_string(index=False))

解释

  • 稳健:如果估计在不同带宽下大致稳定 → 结果可信
  • 不稳健:如果估计随带宽剧烈波动 → 需要进一步调查

2. 多项式阶数的敏感性

警告(Gelman & Imbens 2019):不要使用高阶多项式(

原因

  • 高阶多项式容易过拟合
  • 远离断点的数据会过度影响断点处的估计
  • 置信区间可能过于乐观

建议

  • 使用局部线性,推荐)
  • 如果需要,最多使用局部二次
  • 避免
python
# 比较不同多项式阶数
poly_orders = [1, 2, 3]
poly_results = []

for p in poly_orders:
    df_local = df[np.abs(df['X']) <= 20].copy()
    df_local['X_c'] = df_local['X']
    df_local['D'] = (df_local['X'] >= 0).astype(int)

    # 构建公式
    formula_terms = ['D', 'X_c']
    for k in range(2, p + 1):
        formula_terms.append(f'I(X_c**{k})')

    # 添加交互项
    for term in formula_terms[1:]:
        formula_terms.append(f'D:{term}')

    formula = 'Y ~ ' + ' + '.join(formula_terms)

    model = smf.ols(formula, data=df_local).fit()

    poly_results.append({
        'polynomial_order': p,
        'effect': model.params['D'],
        'se': model.bse['D'],
        'ci_lower': model.conf_int().loc['D', 0],
        'ci_upper': model.conf_int().loc['D', 1]
    })

poly_df = pd.DataFrame(poly_results)

print("\n" + "=" * 80)
print("多项式阶数敏感性分析")
print("=" * 80)
print(poly_df.to_string(index=False))
print("\n建议:使用线性(p=1)或二次(p=2),避免高阶")

3. Donut-hole RDD

动机:排除极接近断点的观测,检验结果是否稳健。

原因

  • 如果存在精确操控,最可能发生在断点附近
  • 如果排除这些观测后结果依然稳健,增强可信度

实施

  • 定义"洞"的大小 (如
  • 排除 的观测
  • 重新估计RDD
python
def donut_rdd(df, X_col, Y_col, cutoff, donut_size, bandwidth):
    """
    Donut-hole RDD

    参数:
    - df: 数据框
    - X_col, Y_col: 变量名
    - cutoff: 断点
    - donut_size: 洞的大小(排除 |X - c| < donut_size 的观测)
    - bandwidth: 带宽
    """
    df_donut = df[
        (np.abs(df[X_col] - cutoff) >= donut_size) &
        (np.abs(df[X_col] - cutoff) <= bandwidth)
    ].copy()

    df_donut['X_c'] = df_donut[X_col] - cutoff
    df_donut['D'] = (df_donut[X_col] >= cutoff).astype(int)

    model = smf.ols(f'{Y_col} ~ D + X_c + D:X_c', data=df_donut).fit()

    return {
        'donut_size': donut_size,
        'effect': model.params['D'],
        'se': model.bse['D'],
        'n_dropped': len(df[(np.abs(df[X_col] - cutoff) < donut_size)]),
        'n_used': len(df_donut)
    }

# 尝试不同洞的大小
donut_sizes = [0, 1, 2, 3, 5]
donut_results = []

for ds in donut_sizes:
    result = donut_rdd(df, 'X', 'Y', 0, ds, 20)
    donut_results.append(result)

donut_df = pd.DataFrame(donut_results)

print("\n" + "=" * 80)
print("Donut-hole RDD 检验")
print("=" * 80)
print(donut_df.to_string(index=False))
print("\n如果估计稳健,说明结果不受极近断点观测的影响")

完整稳健性检验流程

综合报告

python
def rdd_robustness_report(df, X_col, Y_col, cutoff):
    """
    生成完整的 RDD 稳健性检验报告

    参数:
    - df: 数据框
    - X_col: 驱动变量
    - Y_col: 结果变量
    - cutoff: 断点
    """
    from rdrobust import rdrobust

    print("=" * 80)
    print(" " * 25 + "RDD 稳健性检验报告")
    print("=" * 80)

    df = df.copy()
    df['X_c'] = df[X_col] - cutoff
    df['D'] = (df[X_col] >= cutoff).astype(int)

    # 1. 基准估计(CCT 最优带宽)
    print("\n【1】基准估计(CCT 最优带宽)")
    print("-" * 80)
    baseline = rdrobust(y=df[Y_col], x=df[X_col], c=cutoff)
    print(f"最优带宽: {baseline.bws[0]:.2f}")
    print(f"RDD 效应: {baseline.coef[0]:.4f}")
    print(f"稳健 SE: {baseline.se[0]:.4f}")
    print(f"稳健 p-value: {baseline.pval[0]:.4f}")
    print(f"95% CI: [{baseline.ci[0][0]:.4f}, {baseline.ci[0][1]:.4f}]")

    # 2. 带宽敏感性
    print("\n【2】带宽敏感性分析")
    print("-" * 80)
    h_baseline = baseline.bws[0]
    bandwidths = [0.5 * h_baseline, 0.75 * h_baseline, h_baseline,
                  1.25 * h_baseline, 1.5 * h_baseline]

    bw_results = []
    for h in bandwidths:
        df_local = df[np.abs(df['X_c']) <= h]
        if len(df_local) > 50:
            model = smf.ols(f'{Y_col} ~ D + X_c + D:X_c', data=df_local).fit()
            bw_results.append({
                'Bandwidth': f'{h:.2f}',
                'Effect': f"{model.params['D']:.4f}",
                'SE': f"{model.bse['D']:.4f}",
                'N': len(df_local)
            })

    bw_df = pd.DataFrame(bw_results)
    print(bw_df.to_string(index=False))

    # 3. 多项式阶数
    print("\n【3】多项式阶数敏感性")
    print("-" * 80)
    poly_results = []

    for p in [1, 2]:
        df_local = df[np.abs(df['X_c']) <= h_baseline]
        formula_parts = ['D', 'X_c']
        for k in range(2, p + 1):
            formula_parts.append(f'I(X_c**{k})')
        for part in formula_parts[1:]:
            formula_parts.append(f'D:{part}')

        formula = f'{Y_col} ~ ' + ' + '.join(formula_parts)
        model = smf.ols(formula, data=df_local).fit()

        poly_results.append({
            'Polynomial': f'p={p}',
            'Effect': f"{model.params['D']:.4f}",
            'SE': f"{model.bse['D']:.4f}"
        })

    poly_df = pd.DataFrame(poly_results)
    print(poly_df.to_string(index=False))

    # 4. Donut-hole
    print("\n【4】Donut-hole RDD")
    print("-" * 80)
    donut_results = []

    for ds in [0, 1, 2, 5]:
        df_donut = df[
            (np.abs(df['X_c']) >= ds) &
            (np.abs(df['X_c']) <= h_baseline)
        ]
        if len(df_donut) > 50:
            model = smf.ols(f'{Y_col} ~ D + X_c + D:X_c', data=df_donut).fit()
            donut_results.append({
                'Donut': ds,
                'Effect': f"{model.params['D']:.4f}",
                'N_dropped': len(df[np.abs(df['X_c']) < ds]),
                'N_used': len(df_donut)
            })

    donut_df = pd.DataFrame(donut_results)
    print(donut_df.to_string(index=False))

    print("\n" + "=" * 80)
    print(" " * 30 + "报告结束")
    print("=" * 80)

# 生成报告
rdd_robustness_report(df, 'X', 'Y', 0)

实践中的最佳实践

带宽选择的建议

  1. 默认使用 CCT 最优带宽rdrobust 默认)
  2. 报告多个带宽下的估计(如 0.5h, 0.75h, h, 1.25h, 1.5h)
  3. 检查敏感性:如果结果在合理范围内的带宽下稳定 → 可信

多项式阶数的建议

  1. 主结果:使用局部线性(
  2. 稳健性:检查局部二次(
  3. 避免:不要使用

报告清单

论文中应报告的内容

  1. 主效应估计(CCT 最优带宽 + 稳健SE)
  2. 带宽敏感性(表格或图形)
  3. 有效性检验
  • 协变量平衡
  • McCrary 密度检验
  • 安慰剂检验
  1. 多项式阶数敏感性(可选)
  2. Donut-hole 检验(如果担心操控)

关键要点

带宽选择

  1. MSE 最优:IK 和 CCT 方法(推荐 CCT)
  2. 自动化:使用 rdrobust 包的默认设置
  3. 稳健性:报告多个带宽下的结果

稳健性检验

  1. 带宽敏感性:估计应在合理范围内稳定
  2. 多项式阶数:避免高阶(
  3. Donut-hole:排除极近断点观测,检验稳健性

报告规范

  1. 主结果:CCT + 稳健置信区间
  2. 稳健性表格:带宽、多项式、donut-hole
  3. 有效性检验:协变量、密度、安慰剂

本节总结

在本节中,我们学习了:

  • 带宽选择的理论(偏差-方差权衡)
  • 自动带宽选择方法(IK, CCT)
  • 敏感性分析(带宽、多项式阶数)
  • Donut-hole RDD
  • 完整的稳健性检验流程

关键教训

"RDD 的可信度不仅取决于点估计,更取决于结果在各种规格下的稳健性。审稿人会仔细检查这些!"

下一步:在 第5节 中,我们将复现经典RDD研究,包括 Angrist & Lavy (1999) 和 Lee (2008)。


严格稳健性检验,让你的研究经得起考验!

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