机器学习与因果推断 - 第四讲:匹配与分层
基于可观测变量的选择偏差校正:完整讲义
1 引言:从随机实验到观测研究
1.1 为什么需要匹配方法?
在理想的随机对照试验(RCT)中,处理组和对照组是随机分配的,这保证了两组在各方面都可比。用潜在结果框架表示:
这意味着处理分配与潜在结果独立,选择偏差为零。
然而,在实际的观测研究中:
- 处理不是随机分配的——个体是否接受处理往往与他们的潜在结果相关
- 存在选择偏差——接受处理的个体可能本身就具有某些特征
- 直接比较会产生偏误—— 不等于真实的处理效应
匹配方法的核心目标:在观测数据中构建一个”虚拟的”随机实验,通过找到与处理组个体相似的对照组个体,消除选择偏差。
1.2 选择偏差的分解
观测到的均值差异可以分解为:
这个分解揭示了关键问题:
- ATT(处理组平均处理效应):我们真正想要的因果效应
- 选择偏差:处理组和对照组在未接受处理时的结果差异,反映了两组的根本差异
匹配法的基本思想:如果我们能找到与处理组”可比”的对照组(即),选择偏差就能消除。
1.3 类比理解
想象你要比较两种教学方法的效果:
- 处理组:使用新教学方法的学生
- 对照组:使用传统方法的学生
问题:如果处理组学生普遍更聪明、家庭条件更好,直接比较是不公平的。
匹配法的解决方案:为每个使用新教学法的学生,找一个智商、家庭背景相近的传统教学法学生进行对比。这样,两组在这些关键特征上就平衡了,差异可以归因于教学方法本身。
2 条件可忽略性假设
2.1 定义与含义
条件可忽略性(Conditional Ignorability)
在给定协变量的情况下,处理分配与潜在结果独立:
这也称为无混淆性(Unconfoundedness)或基于可观测变量的选择(Selection on Observables)。
这个假设意味着什么?
- 在同一层内,处理分配可以视为随机
- 所有混淆变量都被观测到并包含在中
- 我们只需要比较”相似”的个体
2.2 直观理解
条件可忽略性说的是:一旦我们考虑了所有相关的协变量,处理分配就不再包含关于潜在结果的额外信息。
例子:在研究教育对收入的影响时
- 能力是一个重要的混淆变量
- 如果不控制能力,教育选择会与收入潜力相关(选择偏差)
- 如果在能力相同的群体内比较,教育选择可以视为”近似随机”
2.3 局限性
重要警告
条件可忽略性是一个强假设,在应用中需要注意:
- 不可观测的混淆变量——如果存在未观测的因素同时影响处理和结果,匹配无法消除其带来的偏误
- 需要丰富的协变量数据——越多的相关协变量,假设越可信
- 重叠假设(Overlap)必须满足——所有值的处理概率都要严格在0和1之间
3 匹配法的基本原理
3.1 什么是匹配?
基本思想:为每个处理组个体,在对照组中找到”最相似”的个体进行配对。
相似性的定义:
在协变量空间上”接近”的个体被认为是相似的。需要定义”距离”或”相似度”度量。
常用距离度量:
欧氏距离(Euclidean Distance):
马氏距离(Mahalanobis Distance): 其中是协变量的协方差矩阵,考虑了变量间的相关性
3.2 精确匹配 vs 近似匹配
3.2.1 精确匹配(Exact Matching)
- 要求:对照个体在所有协变量上与处理个体完全相同
- 优点:理论上理想,如果找到匹配则没有近似误差
- 缺点:
- 实践中难以实现(尤其协变量是连续的时候)
- 当协变量维度高时出现”维度灾难”
- 很多处理个体可能找不到精确匹配
3.2.2 近似匹配(Approximate Matching)
- 要求:找到在协变量空间上”最接近”的个体
- 优点:更容易找到匹配,实践中更可行
- 常用方法:
- 最近邻匹配(Nearest Neighbor Matching)
- 卡尺匹配(Caliper Matching)
- 核匹配(Kernel Matching)
- 分层/子分类(Subclassification)
3.3 匹配类型:一对一 vs 一对多
| 匹配类型 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 1:1 匹配 | 每个处理个体匹配1个对照 | 方差小,配对简单 | 可能丢弃有用数据 |
| 1:k 匹配 | 每个处理个体匹配k个对照 | 使用更多数据,偏差小 | 方差可能增大 |
| 有放回匹配 | 对照个体可重复使用 | 避免丢弃处理个体 | 对照组样本不独立 |
| 无放回匹配 | 每个对照最多使用一次 | 样本独立性更好 | 可能匹配质量下降 |
选择建议:
- 样本量充足时:使用1:1无放回匹配
- 对照组样本有限时:考虑有放回匹配或1:k匹配
- 的选择通常是2-5,需要在偏差和方差之间权衡
4 倾向得分匹配(PSM)
4.1 倾向得分的定义
倾向得分(Propensity Score)
在给定协变量的情况下,个体接受处理的概率:
Rosenbaum & Rubin (1983) 的重要定理:
如果(条件可忽略性),那么:
这意味着:控制倾向得分相当于控制了所有协变量。
4.2 为什么倾向得分有用?
- 降维:将多维协变量压缩为一维得分
- 避免维度灾难:高维匹配中的问题得到缓解
- 平衡协变量:在倾向得分上匹配,等价于在所有协变量上平衡
直观理解:
倾向得分相似的个体,其协变量分布也是相似的。因此,比较相同倾向得分下的处理组和对照组,就像是在比较”可比”的个体。
4.3 倾向得分的估计
4.3.1 Logistic 回归
最常用的方法是Logistic回归:
特点: - 线性于Logit变换 - 系数解释:表示增加一个单位,对数几率的变化 - 预测概率自动在[0,1]范围内
4.3.2 Probit 回归
另一种选择是Probit模型:
其中是标准正态分布的CDF。
特点: - 结果与Logistic回归通常很接近 - 系数解释略有不同
4.3.3 机器学习方法
对于复杂的非线性关系,可以使用:
- 随机森林
- 梯度提升(XGBoost, LightGBM)
- 神经网络
注意:虽然机器学习方法可以更灵活地估计倾向得分,但过度拟合可能导致问题。需要在灵活性和稳定性之间权衡。
4.4 PSM的实施步骤
4.4.1 步骤1:估计倾向得分
使用Logit或Probit模型估计倾向得分,为每个个体计算。
4.4.2 步骤2:检验共同支撑域(Common Support)
共同支撑域是指处理组和对照组倾向得分分布重叠的区域。
为什么重要?
- 倾向得分分布应该有重叠
- 在没有重叠的区域,无法找到合适的匹配
- 需要剔除没有共同支撑的个体
常用方法:
- 直方图或核密度图检查
- 设定截断值(如0.01-0.99)剔除极端值
- “最小-最大”规则:只保留倾向得分在另一组范围内的个体
4.4.3 步骤3:匹配处理组和对照组
在倾向得分空间上进行匹配。常用方法包括最近邻、卡尺匹配、核匹配等。
5 匹配方法详解
5.1 最近邻匹配(Nearest Neighbor Matching)
基本思想:为每个处理个体找到倾向得分最接近的对照个体。
算法:
- 对于处理组中的每个个体,计算其倾向得分
- 在对照组中找到使得最小
- 将与配对
变体:
- 有放回/无放回:对照个体是否可以被多次使用
- 1:1/1:k:每个处理个体匹配1个或多个对照
- 贪婪/最优:贪婪算法顺序匹配,最优算法全局优化
优点: - 简单高效 - 易于理解和实现
缺点: - 可能产生”坏”匹配(倾向得分差距很大) - 贪婪算法可能导致次优的全局匹配
5.2 卡尺匹配(Caliper Matching)
基本思想:设定最大距离阈值,只匹配在阈值内的对照。
卡尺(Caliper)的设定:
只匹配满足的对照个体,其中是卡尺值。
常用卡尺:
即倾向得分标准差的0.2倍。
Rosenbaum & Rubin (1985)的建议:
- 卡尺为0.2倍标准差时,可以消除95%的偏差
- 卡尺过大会引入坏匹配
- 卡尺过小会丢弃太多样本
优点: - 避免”坏”匹配 - 提高匹配质量
缺点: - 可能丢弃部分处理个体(没有匹配的对照) - 需要选择合适的卡尺值
5.3 核匹配(Kernel Matching)
基本思想:使用所有对照个体,但按距离加权。
估计公式:
其中: - 是核函数(如高斯核、Epanechnikov核) - 是带宽参数 - 离处理个体越近的对照个体权重越大
优点: - 使用所有对照数据,不丢弃信息 - 平滑的权重函数
缺点: - 计算量较大 - 需要选择核函数和带宽
6 分层估计(Subclassification)
6.1 分层估计的基本思想
核心思想:将样本按倾向得分分成若干层(strata),在层内进行简单比较。
为什么分层有效?
在同一层内,处理组和对照组的倾向得分相近,这意味着:
- 协变量分布相似
- 处理分配可以视为”近似随机”
- 组间差异可归因于处理效应
6.2 分层步骤
- 按倾向得分排序:将所有样本按估计的倾向得分排序
- 分成个层:通常或
- 在每层内计算处理效应:计算处理组和对照组的均值差
- 加权平均得到总体效应:根据层内样本量加权
6.3 分层估计公式
层内处理效应估计:
其中和分别是第层内处理组和对照组的平均结果。
总体处理效应估计:
其中是第层的样本量,是总样本量。
Cochran (1968)的经典结果:
- 5-10层通常足够消除95%以上的选择偏差
- 层数过多会导致层内样本过少,估计不稳定
- 层数过少会导致层内协变量不平衡
7 平衡性检验
7.1 为什么需要平衡性检验?
匹配的目的是使处理组和对照组在协变量上可比。
如果匹配后仍然不平衡:
- 匹配后仍存在混淆
- 处理效应估计有偏
- 外推问题(缺乏共同支撑域)
7.2 标准化偏差
定义:
经验法则(Rosenbaum & Rubin, 1985):
| 标准化偏差 | 解释 |
|---|---|
| 可接受,平衡良好 | |
| 需注意,边界情况 | |
| 严重不平衡,需要改进 |
匹配后的目标:所有协变量的标准化偏差都小于0.1。
7.3 可视化平衡性检查
7.3.1 匹配前的特征
- 协变量分布差异明显
- 倾向得分分布重叠少
- 标准化偏差大
7.3.2 匹配后的理想状态
- 协变量均值接近
- 分布形状相似
- 共同支撑域充足
7.3.3 常用可视化工具
- 箱线图:比较匹配前后分布
- QQ图:检查分位数对应关系
- 密度图:比较倾向得分分布
- Love图:展示标准化偏差(匹配前后对比)
8 实践案例:LaLonde 职业培训项目
8.1 案例背景
LaLonde (1986) 研究
评估美国国家支持工作示范项目(NSW)的效果。
研究设计:
- 处理组:接受职业培训的低收入工人
- 对照组:未接受培训的对照人群
- 结果变量:1978年实际收入
为什么这是经典案例?
这是因果推断方法研究的经典数据集,被广泛用于评估匹配、DID等方法的有效性。研究面临的挑战包括:
- 处理组和对照组在可观测特征上差异显著
- 培训参与者可能本身更有动力
- 直接回归分析会产生选择偏差
8.2 数据生成与探索
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['Source Han Sans SC', 'Noto Sans CJK SC',
'WenQuanYi Micro Hei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 加载LaLonde数据(使用模拟数据展示方法)
np.random.seed(42)
n = 1000
# 生成协变量
age = np.random.normal(25, 7, n)
education = np.random.normal(10, 2.5, n)
black = np.random.binomial(1, 0.5, n)
hispanic = np.random.binomial(1, 0.2, n)
married = np.random.binomial(1, 0.3, n)
nodegree = np.random.binomial(1, 0.6, n)
# 生成收入前置变量
re74 = np.random.lognormal(8, 1.5, n)
re75 = np.random.lognormal(8, 1.5, n)
# 处理变量(受协变量影响)
X_vars = np.column_stack([age, education, black, hispanic, married, nodegree, re74, re75])
X_scaled = (X_vars - X_vars.mean(axis=0)) / X_vars.std(axis=0)
# 倾向得分(Logit模型)
logit_ps = (0.3 * X_scaled[:, 0] - 0.5 * X_scaled[:, 1] + 0.8 * X_scaled[:, 2]
+ 0.3 * X_scaled[:, 5] - 0.2 * X_scaled[:, 6] / 1000)
ps = 1 / (1 + np.exp(-logit_ps))
treat = np.random.binomial(1, ps)
# 真实处理效应
true_effect = 1794
# 结果变量(收入)
re78_base = (1000 + 50 * age + 200 * education + 300 * married
+ 0.3 * re74 + 0.3 * re75 + np.random.normal(0, 3000, n))
re78 = re78_base + true_effect * treat
# 创建数据框
data = pd.DataFrame({
'treat': treat,
'age': age,
'education': education,
'black': black,
'hispanic': hispanic,
'married': married,
'nodegree': nodegree,
're74': re74,
're75': re75,
're78': re78,
'ps': ps
})
print(f"样本量: {n}")
print(f"处理组: {treat.sum()} ({treat.mean()*100:.1f}%)")
print(f"对照组: {(1-treat).sum()} ({(1-treat).mean()*100:.1f}%)")8.3 协变量平衡性检查(匹配前)
# 计算匹配前的标准化偏差
covariates = ['age', 'education', 'black', 'hispanic', 'married', 'nodegree', 're74', 're75']
balance_before = []
for var in covariates:
treated_mean = data[data['treat']==1][var].mean()
control_mean = data[data['treat']==0][var].mean()
treated_std = data[data['treat']==1][var].std()
control_std = data[data['treat']==0][var].std()
# 标准化偏差
std_bias = (treated_mean - control_mean) / np.sqrt((treated_std**2 + control_std**2) / 2)
balance_before.append({
'Variable': var,
'Treated_Mean': f"{treated_mean:.2f}",
'Control_Mean': f"{control_mean:.2f}",
'Std_Bias': f"{std_bias:.3f}"
})
balance_df = pd.DataFrame(balance_before)
print("匹配前协变量平衡性:")
print("=" * 60)
print(balance_df.to_string(index=False))
print("\n标准化偏差 > 0.2 表示严重不平衡!")预期结果:
匹配前,处理组和对照组在协变量上存在显著差异。特别是种族、婚姻状况、前置收入等变量,标准化偏差可能超过0.2,表明严重不平衡。
8.4 倾向得分估计
# 使用Logistic回归估计倾向得分
X = data[['age', 'education', 'black', 'hispanic', 'married', 'nodegree', 're74', 're75']]
y = data['treat']
# 标准化收入变量
X_scaled = X.copy()
X_scaled['re74'] = X_scaled['re74'] / 1000
X_scaled['re75'] = X_scaled['re75'] / 1000
# 拟合Logistic回归
logit_model = LogisticRegression(max_iter=1000)
logit_model.fit(X_scaled, y)
# 预测倾向得分
data['ps_estimated'] = logit_model.predict_proba(X_scaled)[:, 1]
print("Logistic回归系数:")
coef_df = pd.DataFrame({
'Variable': X.columns,
'Coefficient': logit_model.coef_[0]
})
print(coef_df.to_string(index=False))
print(f"\n处理组倾向得分均值: {data[data['treat']==1]['ps_estimated'].mean():.3f}")
print(f"对照组倾向得分均值: {data[data['treat']==0]['ps_estimated'].mean():.3f}")8.5 最近邻匹配
# 分离处理组和对照组
treated = data[data['treat'] == 1].copy()
control = data[data['treat'] == 0].copy()
# 使用倾向得分进行1:1最近邻匹配
nbrs = NearestNeighbors(n_neighbors=1).fit(control['ps_estimated'].values.reshape(-1, 1))
distances, indices = nbrs.kneighbors(treated['ps_estimated'].values.reshape(-1, 1))
# 获取匹配的对照组个体
matched_control_indices = control.iloc[indices.flatten()].index
matched_data = pd.concat([treated, data.loc[matched_control_indices]])
print(f"匹配前处理组数量: {len(treated)}")
print(f"匹配后对照组数量: {len(matched_control_indices)}")
print(f"匹配后总样本量: {len(matched_data)}")
# 计算平均匹配距离
mean_distance = distances.mean()
print(f"\n平均倾向得分距离: {mean_distance:.4f}")8.6 匹配后平衡性检查
# 计算匹配后的标准化偏差
balance_after = []
for var in covariates:
treated_mean = matched_data[matched_data['treat']==1][var].mean()
control_mean = matched_data[matched_data['treat']==0][var].mean()
treated_std = matched_data[matched_data['treat']==1][var].std()
control_std = matched_data[matched_data['treat']==0][var].std()
std_bias = (treated_mean - control_mean) / np.sqrt((treated_std**2 + control_std**2) / 2)
balance_after.append({
'Variable': var,
'Std_Bias_After': std_bias
})
# 合并前后结果
balance_comparison = pd.DataFrame(balance_before)
balance_comparison['Std_Bias_After'] = [x['Std_Bias_After'] for x in balance_after]
balance_comparison['Std_Bias'] = balance_comparison['Std_Bias'].astype(float)
print("标准化偏差对比:")
print("=" * 50)
print(balance_comparison[['Variable', 'Std_Bias', 'Std_Bias_After']].to_string(index=False))预期改善:
匹配后,所有协变量的标准化偏差应该大幅减小,大部分应该低于0.1的可接受阈值。
8.7 匹配后处理效应估计
# 匹配前的朴素估计
naive_est = data[data['treat']==1]['re78'].mean() - data[data['treat']==0]['re78'].mean()
# 匹配后的估计
matched_est = (matched_data[matched_data['treat']==1]['re78'].mean()
- matched_data[matched_data['treat']==0]['re78'].mean())
# 回归调整(匹配后)
X_matched = sm.add_constant(matched_data[['treat']])
model_matched = sm.OLS(matched_data['re78'], X_matched).fit()
reg_est = model_matched.params['treat']
print("处理效应估计结果对比:")
print("=" * 60)
print(f"真实处理效应: ${true_effect:,.0f}")
print(f"朴素估计(匹配前): ${naive_est:,.0f} (偏差: {naive_est - true_effect:,.0f})")
print(f"匹配后估计: ${matched_est:,.0f} (偏差: {matched_est - true_effect:,.0f})")
print(f"回归调整估计: ${reg_est:,.0f} (偏差: {reg_est - true_effect:,.0f})")
print("=" * 60)
print("\n结论: 匹配显著降低了选择偏差!")预期结果:
- 朴素估计可能显著高估或低估真实效应(取决于选择偏差的符号)
- 匹配后估计应该更接近真实处理效应
- 回归调整可以进一步提高估计的精确度
9 匹配法的局限性与拓展
9.1 匹配法的局限性
9.1.1 1. 不可观测混淆变量
匹配只能控制可观测的混淆变量。如果存在未观测的混淆因素(同时影响处理和结果),估计仍有偏。
应对策略:
- 敏感性分析(评估未观测混淆需要多强才能解释掉结果)
- 使用工具变量法(如果有有效的工具变量)
- 使用双重差分(如果有面板数据)
9.1.2 2. 违背重叠假设
- 某些处理组个体没有合适的对照匹配
- 倾向得分分布不重叠的区域需要剔除
- 可能导致样本选择偏差
应对策略:
- 检查共同支撑域并剔除不满足的个体
- 报告样本量变化
- 评估剔除样本的特征
9.1.3 3. 高维协变量挑战
- 协变量维度高时,匹配质量下降
- 倾向得分模型设定困难
- 维度灾难问题
应对策略:
- 使用正则化方法估计倾向得分
- 使用机器学习方法
- 协变量平衡倾向得分(CBPS)
9.2 匹配法的变体与拓展
9.2.1 协变量平衡倾向得分(CBPS)
Imai & Ratkovic (2014)
- 同时估计倾向得分和平衡协变量
- 广义矩估计框架
- 更好的平衡性保证
基本思想:
传统PSM先估计倾向得分,然后检验平衡性。CBPS将平衡性直接纳入估计过程,寻找既能预测处理又能平衡协变量的倾向得分模型。
9.2.2 熵平衡(Entropy Balancing)
Hainmueller (2012)
- 直接约束协变量矩条件
- 自动确定权重
- 精确的协变量平衡
基本思想:
不给每个对照个体相同的权重,而是寻找一组权重,使得加权后的对照组在协变量的各阶矩上都与处理组匹配。
9.2.3 机器学习方法
- 使用随机森林、梯度提升估计倾向得分
- 神经网络处理复杂非线性关系
- 需要注意过拟合问题
9.3 双重稳健估计(AIPW)
Augmented Inverse Probability Weighting
结合倾向得分和结果回归:
双重稳健性:
只要倾向得分模型或结果模型有一个正确,估计就是一致的。
优势:
- 双重稳健性
- 半参数效率界(达到理论上的最小方差)
- 比单纯匹配或回归更可靠
10 总结
10.1 本讲要点
- 匹配法原理
- 基于可观测变量构建可比对照组
- 消除选择偏差,恢复因果效应
- 依赖条件可忽略性假设
- 倾向得分匹配(PSM)
- 将多维协变量压缩为一维得分
- 最近邻、卡尺、核匹配方法
- 需要检查共同支撑域
- 分层估计
- 按倾向得分分层比较
- 加权平均得到总体效应
- 5-10层通常足够
- 平衡性检验
- 标准化偏差 < 0.1 为可接受
- 可视化检查分布重叠
- 确保匹配质量
10.2 实践建议
使用匹配法的检查清单:
推荐工具:
- MatchIt (R包):全面的匹配方法实现
- CausalML (Python):机器学习因果推断
- EconML (Python):双重稳健估计
- sklearn (Python):最近邻匹配基础
10.3 下一步学习
下一讲将介绍工具变量法(Instrumental Variables),用于处理存在未观测混淆变量的情况:
- 当条件可忽略性假设不满足时
- 利用工具变量识别因果效应
- 两阶段最小二乘法(2SLS)
- 弱工具变量问题
核心思想:
工具变量满足:
- 相关性:与处理相关
- 排他性:只通过影响结果
这种方法可以处理更复杂的选择偏差问题,但需要找到有效的工具变量。