Skip to content

2.5 识别策略与有效性

"No causation without manipulation.""没有操纵就没有因果关系。"— Paul Holland, Statistician (统计学家)

因果推断的核心假设与威胁因素


本节目标

  • 理解因果识别的核心假设(SUTVA、独立性)
  • 掌握内部有效性与外部有效性的区别
  • 识别 RCT 的威胁因素
  • 学习敏感性分析方法

因果识别的核心假设

什么是"识别"(Identification)?

因果识别:能否从观测数据中唯一确定因果效应

通俗理解

  • 不可识别:数据与多个不同的因果效应都兼容(无法区分)
  • 可识别:数据只与一个因果效应兼容(可以估计)

1️⃣ SUTVA 假设

定义

SUTVA(Stable Unit Treatment Value Assumption):稳定单元处理值假设

包含两个子假设:

(1) 无干预溢出(No Interference)

含义:个体 的潜在结果只依赖于自己的处理状态,不受他人处理状态影响

案例

  • 满足:药物试验(每人独立服药)
  • 违反:疫苗接种(群体免疫效应)、教育政策(同伴效应)

(2) 处理一致性(No Hidden Variations of Treatment)

含义:处理只有一种形式,没有隐藏的变异

案例

  • 违反:"在线课程"可能包括:
  • 直播课(高互动)
  • 录播课(低互动)
  • 自学材料(无互动)

不同形式的"在线课程"效应可能不同

SUTVA 违反的后果

python
import numpy as np
import pandas as pd

# 模拟溢出效应
np.random.seed(42)
n = 100

# 生成社交网络(邻居关系)
friends = np.random.randint(0, n, size=(n, 3))  # 每人 3 个朋友

# 随机分配
data = pd.DataFrame({
    'id': range(n),
    'treatment': np.random.binomial(1, 0.5, n)
})

# 计算朋友中接受处理的比例
data['friends_treated'] = 0
for i in range(n):
    friend_ids = friends[i]
    data.loc[i, 'friends_treated'] = data.loc[friend_ids, 'treatment'].mean()

# 结果变量(包含溢出效应)
# 自己的处理 + 朋友的处理都有影响
data['Y'] = (100 +
             30 * data['treatment'] +  # 直接效应
             15 * data['friends_treated'] +  # 溢出效应
             np.random.normal(0, 10, n))

# 简单对比会混合直接效应和溢出效应
simple_diff = (data[data['treatment'] == 1]['Y'].mean() -
               data[data['treatment'] == 0]['Y'].mean())

print(f"简单对比: {simple_diff:.2f}")
print("真实直接效应: 30")
print("溢出效应: 15 × (朋友接受处理的比例)")
print("\n️ SUTVA 违反 → 简单对比估计有偏!")

应对 SUTVA 违反

方法适用场景示例
聚类随机化溢出效应局限在群体内以学校为单位随机化
两阶段实验随机化群体和个体先随机选村庄,再随机选村民
网络实验设计已知社交网络结构随机化不相邻节点
结构化模型估计直接效应和溢出效应空间计量模型

2️⃣ 独立性假设(Unconfoundedness)

随机化条件下的独立性

强独立性

含义:潜在结果与处理分配统计独立

RCT 的核心优势:随机化自动满足这个假设

条件独立性

在观察性研究中(无随机化),需要更强的假设:

含义:给定协变量 ,处理分配与潜在结果独立

俗称:"选择基于可观测变量"(Selection on Observables)

检验独立性:平衡性检验

python
from scipy import stats
import pandas as pd
import numpy as np

def independence_test(data, covariates, treatment_col='treatment'):
    """
    检验处理分配与协变量的独立性
    """
    results = []

    for cov in covariates:
        if data[cov].dtype in ['float64', 'int64']:
            # 连续变量:t 检验
            treated = data[data[treatment_col] == 1][cov]
            control = data[data[treatment_col] == 0][cov]
            stat, p_value = stats.ttest_ind(treated, control)
            test_type = 't-test'
        else:
            # 分类变量:卡方检验
            contingency = pd.crosstab(data[cov], data[treatment_col])
            stat, p_value, _, _ = stats.chi2_contingency(contingency)
            test_type = 'chi2'

        results.append({
            '协变量': cov,
            '检验类型': test_type,
            '统计量': stat,
            'p-value': p_value,
            '平衡': '' if p_value > 0.05 else ''
        })

    return pd.DataFrame(results)

# 示例
np.random.seed(42)
n = 200

data = pd.DataFrame({
    'treatment': np.random.binomial(1, 0.5, n),
    'age': np.random.normal(30, 10, n),
    'income': np.random.lognormal(10, 1, n),
    'gender': np.random.choice(['M', 'F'], n),
    'education': np.random.choice(['高中', '本科', '研究生'], n)
})

balance_results = independence_test(
    data,
    covariates=['age', 'income', 'gender', 'education']
)

print(balance_results)

3️⃣ 内部有效性(Internal Validity)

定义

内部有效性:因果推断在研究样本内是否正确

含义:估计量能否无偏且一致地估计样本内的因果效应

威胁因素

1. 选择偏误(Selection Bias)

来源:处理组和对照组基线不可比

RCT 中可能出现的情况

  • 随机化失败(技术问题)
  • 样本量太小(随机波动)
  • 分层不当

检验:平衡性检验(见上文)

应对

  • 重新随机化
  • 回归控制不平衡变量
  • 匹配或 IPW(逆概率加权)

2. 样本流失(Attrition)

定义:部分参与者退出研究,导致结果数据缺失

python
# 模拟样本流失
np.random.seed(42)
n = 500

data = pd.DataFrame({
    'treatment': np.random.binomial(1, 0.5, n),
    'baseline_health': np.random.normal(50, 10, n)
})

# 潜在结果
data['Y0'] = 50 + 0.5 * data['baseline_health'] + np.random.normal(0, 5, n)
data['Y1'] = data['Y0'] + 10 + np.random.normal(0, 5, n)

# 样本流失(健康状况差的人更可能退出)
attrition_prob = 1 / (1 + np.exp((data['baseline_health'] - 40) / 5))
data['attrited'] = np.random.binomial(1, attrition_prob)

# 观测结果(只有未退出的人有数据)
data['Y_obs'] = np.where(data['treatment'] == 1, data['Y1'], data['Y0'])
data.loc[data['attrited'] == 1, 'Y_obs'] = np.nan

# 完整样本 ATE
complete_ATE = data['Y1'].mean() - data['Y0'].mean()

# 有流失的 ATE
observed_data = data.dropna(subset=['Y_obs'])
attrited_ATE = (observed_data[observed_data['treatment'] == 1]['Y_obs'].mean() -
                observed_data[observed_data['treatment'] == 0]['Y_obs'].mean())

print(f"完整样本 ATE: {complete_ATE:.2f}")
print(f"有流失的 ATE: {attrited_ATE:.2f}")
print(f"偏误: {attrited_ATE - complete_ATE:.2f}")

# 流失率对比
attrition_by_treatment = data.groupby('treatment')['attrited'].mean()
print("\n流失率:")
print(attrition_by_treatment)

检测方法

  1. 比较两组流失率(应该相似)
  2. 检查流失者与留存者的基线特征

应对方法

  • Lee Bounds:估计效应的上下界
  • IPW:逆概率加权(对留存概率加权)
  • 敏感性分析:假设不同的流失机制

3. Hawthorne 效应(观察者效应)

定义:参与者知道自己被观察,改变行为

案例

  • 健康研究:知道被监测,更注意饮食和锻炼
  • 教育实验:教师和学生因为被研究而更努力

应对

  • 双盲设计(Double Blind):参与者和研究者都不知道分配
  • Placebo 对照组:对照组接受安慰剂处理
  • 隐蔽测量:使用行政数据而非问卷

4. 溢出效应(Spillover)

见 SUTVA 部分

5. 霍桑效应的反面:John Henry 效应

定义:对照组因为不想"输"而额外努力

案例:对照学校听说另一所学校在试点新教学法,竞争性地提高教学质量


4️⃣ 外部有效性(External Validity)

定义

外部有效性:因果效应能否推广到其他总体

威胁因素

1. 样本选择偏误

问题:实验样本不代表目标总体

案例

  • 大学生样本 → 推广到全社会?
  • 志愿者样本 → 推广到强制参与?
  • 单一地区 → 推广到全国?
python
# 模拟样本选择偏误
np.random.seed(42)

# 总体(N = 10,000)
population = pd.DataFrame({
    'id': range(10000),
    'ability': np.random.normal(100, 20, 10000)
})

# 真实 ATE(在总体中)
population['tau'] = 10 + 0.1 * population['ability']
true_population_ATE = population['tau'].mean()

# 实验样本(只有高能力者参加)
sample_prob = 1 / (1 + np.exp(-(population['ability'] - 100) / 10))
sample = population[np.random.binomial(1, sample_prob, 10000) == 1].copy()

# 在样本中进行 RCT
sample['treatment'] = np.random.binomial(1, 0.5, len(sample))
sample['Y0'] = 50 + 0.5 * sample['ability']
sample['Y1'] = sample['Y0'] + sample['tau']
sample['Y_obs'] = np.where(sample['treatment'] == 1, sample['Y1'], sample['Y0'])

# 估计 ATE(在样本中)
sample_ATE = (sample[sample['treatment'] == 1]['Y_obs'].mean() -
              sample[sample['treatment'] == 0]['Y_obs'].mean())

print(f"总体 ATE: {true_population_ATE:.2f}")
print(f"样本 ATE: {sample_ATE:.2f}")
print(f"外部有效性偏误: {sample_ATE - true_population_ATE:.2f}")

print(f"\n总体平均能力: {population['ability'].mean():.2f}")
print(f"样本平均能力: {sample['ability'].mean():.2f}")

2. 实验环境与真实环境不同

问题:实验条件过于理想化

案例

  • 实验室实验 → 真实决策环境
  • 小规模试点 → 大规模推广
  • 短期效应 → 长期效应

3. 处理效应的情境依赖性

问题:效应依赖于特定的背景条件

案例

  • 在线教育在疫情期间效果好 → 疫情后呢?
  • 某政策在经济繁荣期有效 → 衰退期呢?

提升外部有效性的方法

方法描述
多地点重复实验在不同地区、不同时间重复
异质性分析研究不同子群的效应,识别边界条件
Meta 分析综合多个研究的结果
重新加权按总体分布对样本加权
python
# 重新加权提升外部有效性
from sklearn.linear_model import LogisticRegression

# 1. 估计样本选择概率(倾向性得分)
X = population[['ability']]
y = np.isin(population['id'], sample['id']).astype(int)

ps_model = LogisticRegression()
ps_model.fit(X, y)
population['ps'] = ps_model.predict_proba(X)[:, 1]

# 2. 计算权重
sample_with_weights = population[population['id'].isin(sample['id'])].copy()
sample_with_weights['weight'] = 1 / sample_with_weights['ps']

# 3. 加权估计 ATE
# (这里简化处理,实际需要结合处理分配)
weighted_mean_tau = (sample_with_weights['tau'] * sample_with_weights['weight']).sum() / sample_with_weights['weight'].sum()

print(f"未加权 ATE: {sample['tau'].mean():.2f}")
print(f"加权 ATE: {weighted_mean_tau:.2f}")
print(f"总体 ATE: {true_population_ATE:.2f}")

5️⃣ 敏感性分析(Sensitivity Analysis)

目的

评估结果对假设违反的稳健性

方法 1:遗漏变量偏误分析

问题:如果存在未观测的混淆变量,结果会改变多少?

python
def sensitivity_analysis_omitted_variable(data, treatment, outcome, r2_confounder_treatment, r2_confounder_outcome):
    """
    遗漏变量敏感性分析

    参数:
    - r2_confounder_treatment: 遗漏变量能解释处理变异的比例
    - r2_confounder_outcome: 遗漏变量能解释结果变异的比例
    """
    import statsmodels.api as sm

    # 观测到的 ATE
    X = sm.add_constant(data[treatment])
    model = sm.OLS(data[outcome], X).fit()
    observed_ATE = model.params[treatment]

    # 计算偏误
    # 简化公式(Cinelli & Hazlett, 2020)
    bias = np.sqrt(r2_confounder_treatment * r2_confounder_outcome) * data[outcome].std()

    # 调整后的估计
    adjusted_ATE_upper = observed_ATE + bias
    adjusted_ATE_lower = observed_ATE - bias

    return {
        '观测 ATE': observed_ATE,
        '偏误上限': bias,
        '调整后 ATE 上界': adjusted_ATE_upper,
        '调整后 ATE 下界': adjusted_ATE_lower
    }

# 示例
result = sensitivity_analysis_omitted_variable(
    sample,
    treatment='treatment',
    outcome='Y_obs',
    r2_confounder_treatment=0.1,  # 遗漏变量解释 10% 的处理变异
    r2_confounder_outcome=0.2      # 遗漏变量解释 20% 的结果变异
)

print("敏感性分析结果:")
for k, v in result.items():
    print(f"  {k}: {v:.2f}")

方法 2:样本流失的 Lee Bounds

python
def lee_bounds(data, treatment, outcome, attrited):
    """
    Lee (2009) 边界估计
    处理样本流失导致的选择偏误
    """
    # 流失率
    attrition_treated = data[data[treatment] == 1][attrited].mean()
    attrition_control = data[data[treatment] == 0][attrited].mean()

    # 计算需要修剪的比例
    trim_prop = abs(attrition_treated - attrition_control)

    # 获取非流失样本
    observed = data[data[attrited] == 0].copy()

    # 上界:修剪处理组的高值
    if attrition_treated > attrition_control:
        treated_obs = observed[observed[treatment] == 1][outcome].sort_values(ascending=False)
        n_trim = int(len(treated_obs) * trim_prop / (1 - attrition_treated))
        treated_trimmed_upper = treated_obs.iloc[n_trim:]
        upper_bound = treated_trimmed_upper.mean() - observed[observed[treatment] == 0][outcome].mean()
    else:
        upper_bound = np.nan

    # 下界:修剪处理组的低值
    if attrition_treated > attrition_control:
        treated_trimmed_lower = treated_obs.iloc[:-n_trim] if n_trim > 0 else treated_obs
        lower_bound = treated_trimmed_lower.mean() - observed[observed[treatment] == 0][outcome].mean()
    else:
        lower_bound = np.nan

    # 简单估计(不修剪)
    simple_ATE = (observed[observed[treatment] == 1][outcome].mean() -
                  observed[observed[treatment] == 0][outcome].mean())

    return {
        '简单估计': simple_ATE,
        'Lee 下界': lower_bound,
        'Lee 上界': upper_bound
    }

# 使用之前的流失数据
bounds = lee_bounds(data, 'treatment', 'Y_obs', 'attrited')
print("\nLee Bounds:")
for k, v in bounds.items():
    if not np.isnan(v):
        print(f"  {k}: {v:.2f}")

方法 3:Placebo 检验

python
def placebo_test(data, treatment, outcome, placebo_treatment):
    """
    Placebo 检验:使用一个不应该有效应的"虚假处理"
    """
    import statsmodels.api as sm

    # 真实处理的效应
    X_real = sm.add_constant(data[treatment])
    model_real = sm.OLS(data[outcome], X_real).fit()
    real_effect = model_real.params[treatment]
    real_pvalue = model_real.pvalues[treatment]

    # Placebo 处理的"效应"
    X_placebo = sm.add_constant(data[placebo_treatment])
    model_placebo = sm.OLS(data[outcome], X_placebo).fit()
    placebo_effect = model_placebo.params[placebo_treatment]
    placebo_pvalue = model_placebo.pvalues[placebo_treatment]

    print("Placebo 检验:")
    print(f"  真实处理效应: {real_effect:.2f} (p={real_pvalue:.4f})")
    print(f"  Placebo 效应: {placebo_effect:.2f} (p={placebo_pvalue:.4f})")

    if placebo_pvalue > 0.05:
        print("   Placebo 不显著,通过检验")
    else:
        print("   Placebo 显著,可能存在问题(如选择偏误)")

# 示例:使用滞后的处理变量作为 placebo
sample['placebo_treatment'] = np.roll(sample['treatment'], 1)
placebo_test(sample, 'treatment', 'Y_obs', 'placebo_treatment')

识别策略对比

策略识别来源核心假设内部有效性外部有效性常见威胁
RCT随机分配SUTVA⭐⭐⭐⭐⭐⭐⭐⭐样本流失、Hawthorne
DID平行趋势无差异趋势⭐⭐⭐⭐⭐⭐⭐⭐趋势违反、反向因果
RDD连续性连续性假设⭐⭐⭐⭐⭐⭐操纵、函数形式
IV外生冲击排除性约束⭐⭐⭐⭐⭐⭐弱工具变量
PSM选择可观测条件独立性⭐⭐⭐⭐⭐隐藏偏误

小结

核心要点

  1. SUTVA 假设

    • 无溢出效应
    • 处理一致性
    • 违反 → 聚类随机化
  2. 独立性假设

    • RCT 自动满足
    • 平衡性检验验证
  3. 内部有效性

    • 样本内因果推断是否正确
    • 威胁:选择偏误、流失、Hawthorne
  4. 外部有效性

    • 能否推广到其他总体
    • 提升:多地点、异质性分析、重新加权
  5. 敏感性分析

    • 评估假设违反的影响
    • 方法:遗漏变量、Lee Bounds、Placebo

思考题

  1. 理解题:解释"内部有效性高但外部有效性低"的含义,并举例说明。

  2. 案例题:某药物 RCT 发现:

    • 处理组流失率 30%,对照组流失率 15%
    • 完整数据显示 ATE = 10

    问题:

    • (a) 这个 ATE 可信吗?为什么?
    • (b) 如何应对?
  3. 设计题:你在设计一个教育 RCT,担心溢出效应(同班同学互相影响)。

    • (a) SUTVA 会如何被违反?
    • (b) 如何修改实验设计?
    • (c) 如果必须在班级内随机化,如何分析数据?
点击查看答案提示

问题 1

  • 内部有效性高:样本内因果推断正确(如严格的 RCT)
  • 外部有效性低:样本不代表总体(如只在名校做实验)
  • 例子:斯坦福大学的在线课程实验 → 推广到社区大学?

问题 2

  • (a) 不可信!流失率差异很大,可能存在差异流失偏误
  • (b) 使用 Lee Bounds 估计效应的上下界,或 IPW 方法

问题 3

  • (a) 处理学生会影响对照学生(同伴效应)
  • (b) 以班级为单位随机化(聚类随机化)
  • (c) 使用 Cluster-Robust 标准误

下一步

下一节我们将进行 Python 完整实战,整合本章所有知识,分析一个真实的 RCT 数据集。

准备好了吗?


参考文献

  • Rubin, D. B. (1980). "Randomization analysis of experimental data: The Fisher randomization test comment". JASA.
  • Lee, D. S. (2009). "Training, wages, and sample selection: Estimating sharp bounds on treatment effects". Review of Economic Studies.
  • Cinelli, C., & Hazlett, C. (2020). "Making sense of sensitivity: Extending omitted variable bias". Journal of the Royal Statistical Society.
  • Manski, C. F. (1990). "Nonparametric bounds on treatment effects". American Economic Review.

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