机器学习与因果推断 - 第一讲:课程导论

从相关性到因果性:完整讲义

作者
单位

陈志远

中国人民大学商学院

发布于

2026年3月2日

1 课程介绍

1.1 课程目标

本课程旨在培养学生运用数据回答”为什么”的能力,而不仅仅是”是什么”。在人工智能时代,机器学习擅长预测,但因果推断才能揭示机制。本课程将系统介绍:

  1. 经典因果推断方法:潜在结果框架、匹配法、双重差分、工具变量、合成控制
  2. 机器学习方法:回归、决策树、随机森林、广义随机森林、双重机器学习
  3. 实际应用:商业决策、政策评估、异质性效应分析

1.2 为什么要学习因果推断?

在现实商业和政策环境中,我们经常面临以下问题:

  • 营销投入真的带来了销量增长吗?还是高潜力市场本身就增长快?
  • 新产品上线提升了用户留存吗?还是忠诚用户更可能尝试新产品?
  • 培训项目提高了员工绩效吗?还是表现好的员工更可能被选中参加培训?
  • 政策调整促进了经济发展吗?还是经济本身就在周期性上行?

这些问题的核心在于区分相关性因果性。简单比较处理组和对照组往往会得出误导性结论,因为选择性偏误普遍存在。

1.3 课程安排

周次 主题 内容要点
1 课程导入 相关vs因果,课程框架,潜在结果框架入门
2-3 因果推断基础 潜在结果框架深入,混淆变量,识别策略
4 匹配法 倾向得分匹配,精确匹配,重叠条件
5-6 双重差分 DID原理,平行趋势检验,动态效应
7 合成控制法 合成控制原理,安慰剂检验
8 工具变量法 IV原理,两阶段最小二乘,排他性约束
9-10 ML基础 回归,决策树,随机森林,交叉验证
11-12 广义随机森林 异质性处理效应,因果树
13 因果森林 异质性分析,Policy Learning
14 双重机器学习 Double ML原理,Neyman正交性
15-16 项目答辩 期末展示与点评

2 核心问题:相关 vs 因果

2.1 一个看似简单的例子

2.1.1 案例背景:新冠停特效药

某公司研发了新冠治疗特效药”新冠停”。临床数据显示:

城市类型 样本量 康复天数 标准误
有售卖点城市 5,678 15.3天 1.1
无售卖点城市 131,415 10.1天 2.1

表面结论:有药物的城市康复反而更慢(15.3天 vs 10.1天)!

2.1.2 深入分析:为什么观察结果如此反直觉?

关键洞察在于选择机制

  1. 药物分配非随机:药物只在疫情严重的地区销售
  2. 严重程度影响康复:疫情严重地区本身康复就
  3. 混淆变量:简单比较混淆了药物效果疫情严重程度

这是一个典型的选择偏误案例。接受处理的城市(有药物)与未接受处理的城市(无药物)在”疫情严重程度”这一关键维度上不可比。

2.2 模拟实验:Python实现

让我们用Python模拟这个场景,直观展示选择偏误是如何产生的。

2.2.1 数据生成过程

点击查看完整代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 设置随机种子保证可重复性
np.random.seed(42)

# 模拟参数
n_cities = 5000  # 城市数量
true_effect = -2  # 真实药物效果:减少2天康复时间

# 步骤1:生成混淆变量(疫情严重程度)
# 假设严重程度服从标准正态分布
severity = np.random.normal(0, 1, n_cities)

# 步骤2:处理分配机制
# 严重程度 > 0 的城市获得药物(选择偏误来源)
treatment = (severity > 0).astype(int)

# 步骤3:生成结果变量(康复时间)
# 康复时间 = 基础时间 + 严重程度影响 + 药物效果 + 随机误差
recovery = 10 + severity * 3 + treatment * true_effect + np.random.normal(0, 1, n_cities)

# 创建数据框
data = pd.DataFrame({
    'severity': severity,
    'treatment': treatment,
    'recovery': recovery
})

# 显示数据前5行
print("数据预览:")
print(data.head(10))
数据预览:
   severity  treatment   recovery
0  0.496714          1   9.066383
1 -0.138264          0   9.131793
2  0.647689          1   8.147422
3  1.523030          1  12.238999
4 -0.234153          0  10.030369
5 -0.234137          0   8.023357
6  1.579213          1  13.786121
7  0.767435          1  10.790079
8 -0.469474          0   7.857344
9  0.542560          1   9.486150

2.2.2 结果分析

# 计算观察到的平均康复时间
summary_stats = data.groupby('treatment')['recovery'].agg(['mean', 'std', 'count'])
summary_stats.index = ['无药物城市', '有药物城市']
print("\n观察到的统计结果:")
print(summary_stats)

# 计算观察到的"效果"
observed_effect = summary_stats.loc['有药物城市', 'mean'] - summary_stats.loc['无药物城市', 'mean']
print(f"\n观察到的差异: {observed_effect:.2f}天")
print(f"真实药物效果: {true_effect:.2f}天")
print(f"选择偏误: {observed_effect - true_effect:.2f}天")

观察到的统计结果:
            mean       std  count
无药物城市   7.610237  2.095548   2474
有药物城市  10.354289  2.061910   2526

观察到的差异: 2.74天
真实药物效果: -2.00天
选择偏误: 4.74天

关键发现: - 观察结果显示有药物的城市康复时间更长(正效应) - 但真实药物效果是缩短康复时间(负效应) - 差异来源于选择偏误:疫情严重的城市才获得药物

2.2.3 可视化分析

fig, axes = plt.subplots(2, 2, figsize=(12, 8))

# 图1:疫情严重程度分布
axes[0, 0].hist(data[data['treatment']==0]['severity'],
                bins=30, alpha=0.6, label='无药物', color='steelblue', edgecolor='white')
axes[0, 0].hist(data[data['treatment']==1]['severity'],
                bins=30, alpha=0.6, label='有药物', color='coral', edgecolor='white')
axes[0, 0].axvline(0, color='red', linestyle='--', linewidth=2, label='分配阈值')
axes[0, 0].set_xlabel('疫情严重程度', fontsize=11)
axes[0, 0].set_ylabel('城市数量', fontsize=11)
axes[0, 0].set_title('(a) 疫情严重程度分布', fontsize=12, fontweight='bold')
axes[0, 0].legend()
axes[0, 0].grid(alpha=0.3)

# 图2:康复时间分布
axes[0, 1].hist(data[data['treatment']==0]['recovery'],
                bins=30, alpha=0.6, label='无药物', color='steelblue', edgecolor='white')
axes[0, 1].hist(data[data['treatment']==1]['recovery'],
                bins=30, alpha=0.6, label='有药物', color='coral', edgecolor='white')
axes[0, 1].axvline(data[data['treatment']==0]['recovery'].mean(),
                   color='steelblue', linestyle='--', linewidth=2, label=f'均值={data[data["treatment"]==0]["recovery"].mean():.1f}')
axes[0, 1].axvline(data[data['treatment']==1]['recovery'].mean(),
                   color='coral', linestyle='--', linewidth=2, label=f'均值={data[data["treatment"]==1]["recovery"].mean():.1f}')
axes[0, 1].set_xlabel('康复天数', fontsize=11)
axes[0, 1].set_ylabel('城市数量', fontsize=11)
axes[0, 1].set_title('(b) 康复时间分布', fontsize=12, fontweight='bold')
axes[0, 1].legend()
axes[0, 1].grid(alpha=0.3)

# 图3:严重程度与康复时间的关系
axes[1, 0].scatter(data[data['treatment']==0]['severity'],
                   data[data['treatment']==0]['recovery'],
                   alpha=0.3, s=15, color='steelblue', label='无药物')
axes[1, 0].scatter(data[data['treatment']==1]['severity'],
                   data[data['treatment']==1]['recovery'],
                   alpha=0.3, s=15, color='coral', label='有药物')
# 添加趋势线
x_range = np.linspace(-3, 3, 100)
axes[1, 0].plot(x_range, 10 + 3*x_range, 'k--', linewidth=2, label='真实关系')
axes[1, 0].set_xlabel('疫情严重程度', fontsize=11)
axes[1, 0].set_ylabel('康复天数', fontsize=11)
axes[1, 0].set_title('(c) 严重程度与康复时间', fontsize=12, fontweight='bold')
axes[1, 0].legend()
axes[1, 0].grid(alpha=0.3)

# 图4:效应分解
ax = axes[1, 1]
effects = [observed_effect, true_effect, observed_effect - true_effect]
colors = ['orange', 'green', 'red']
labels = ['观察效应', '真实效应', '选择偏误']
bars = ax.barh(labels, effects, color=colors, alpha=0.7, edgecolor='black')
ax.axvline(0, color='black', linewidth=1)
ax.set_xlabel('天数', fontsize=11)
ax.set_title('(d) 效应分解', fontsize=12, fontweight='bold')
ax.grid(alpha=0.3, axis='x')

# 添加数值标签
for bar, val in zip(bars, effects):
    ax.text(val + 0.1 if val >= 0 else val - 0.1, bar.get_y() + bar.get_height()/2,
            f'{val:.2f}', ha='left' if val >= 0 else 'right', va='center', fontweight='bold')

plt.tight_layout()
plt.show()

选择偏误的可视化解释

2.2.4 图形解读

  1. 图(a):有药物的城市(红色)集中在严重程度>0的区域,而无药物的城市(蓝色)集中在严重程度<0的区域。两组在混淆变量上分布完全不同。

  2. 图(b):有药物城市的康复时间均值反而更高,但这只是因为它们疫情更严重,而非药物无效。

  3. 图(c):散点图显示严重程度与康复时间正相关。两组数据的分离导致了观察偏误。

  4. 图(d):效应分解显示观察效应(正)= 真实效应(负)+ 选择偏误(正且很大)。

2.3 R语言验证

同样的分析可以用R语言实现,结果应完全一致(数值可能因随机数生成器差异而略有不同)。

#| eval: true
#| echo: true
#| message: false
#| warning: false

library(tidyverse)

# 设置随机种子
set.seed(42)

# 模拟参数
n <- 5000
true_effect <- -2

# 生成数据
data_r <- tibble(
  severity = rnorm(n),
  treatment = ifelse(severity > 0, 1, 0),
  recovery = 10 + severity * 3 + treatment * true_effect + rnorm(n)
)

# 计算统计量
summary_r <- data_r |>
  group_by(treatment) |>
  summarise(
    mean_recovery = mean(recovery),
    sd_recovery = sd(recovery),
    n = n()
  )

print("R语言统计结果:")
print(summary_r)

# 计算效应
observed_effect_r <- summary_r$mean_recovery[2] - summary_r$mean_recovery[1]
cat("\n观察到的差异:", round(observed_effect_r, 2), "天\n")
cat("真实药物效果:", true_effect, "天\n")
cat("选择偏误:", round(observed_effect_r - true_effect, 2), "天\n")

一致性检验:R和Python的结果应该非常接近(差异小于0.5天),这验证了我们模拟的可靠性。

3 潜在结果框架

3.1 核心概念

3.1.1 潜在结果的定义

Rubin因果模型的核心思想是:每个个体都有两种潜在状态

  • Yi(1)Y_i(1):个体ii接受处理时的结果(潜在结果1)
  • Yi(0)Y_i(0):个体ii未接受处理时的结果(潜在结果0)

关键问题:对于任何个体,我们只能观察到其中一种状态!这被称为因果推断的基本问题

3.1.2 因果效应的定义

个体ii的处理效应(Individual Treatment Effect, ITE)定义为:

τi=Yi(1)Yi(0)\tau_i = Y_i(1) - Y_i(0)

由于无法同时观测两种状态,个体层面的因果效应不可识别

3.2 观察到的结果

我们实际能观测到的结果可以用以下公式表示:

Yi={Yi(1)if Di=1(接受处理)Yi(0)if Di=0(未接受处理)Y_i = \begin{cases} Y_i(1) & \text{if } D_i = 1 \text{(接受处理)} \\ Y_i(0) & \text{if } D_i = 0 \text{(未接受处理)} \end{cases}

等价地写成:

Yi=Yi(0)+Di×[Yi(1)Yi(0)]=Yi(0)+Di×τiY_i = Y_i(0) + D_i \times [Y_i(1) - Y_i(0)] = Y_i(0) + D_i \times \tau_i

这被称为Rubin因果模型的基本方程。

3.3 平均处理效应

由于个体效应不可识别,我们转而关注总体参数

3.3.1 平均处理效应(ATE)

ATE=E[Yi(1)Yi(0)]=E[τi]\text{ATE} = E[Y_i(1) - Y_i(0)] = E[\tau_i]

ATE表示如果随机选择一个人给予处理,预期效果是多少。

3.3.2 处理组平均处理效应(ATT)

ATT=E[Yi(1)Yi(0)|Di=1]\text{ATT} = E[Y_i(1) - Y_i(0) | D_i = 1]

ATT表示实际接受处理的人群从中获益多少,这在政策评估中往往更有意义。

3.4 选择性偏误的数学分解

观察到的组间差异可以分解为:

E[Y|D=1]E[Y|D=0]观察差异=E[Y(1)Y(0)|D=1]ATT+E[Y(0)|D=1]E[Y(0)|D=0]选择偏误\underbrace{E[Y|D=1] - E[Y|D=0]}_{\text{观察差异}} = \underbrace{E[Y(1)-Y(0)|D=1]}_{\text{ATT}} + \underbrace{E[Y(0)|D=1] - E[Y(0)|D=0]}_{\text{选择偏误}}

3.4.1 分解的直观解释

成分 含义 示例
观察差异 简单比较两组均值 参加培训者收入 - 未参加者收入
ATT 处理对接受者的真实效应 培训使收入提高1000元
选择偏误 接受者的”先天优势” 参加培训者本来能力就更强

核心问题:选择偏误往往为正(能力强的人更可能接受处理),导致简单比较高估处理效应。

3.5 随机实验:因果推断的黄金标准

3.5.1 随机化的魔力

如果处理分配DD是完全随机的,则:

DY(0),Y(1)D \perp\!\!\perp Y(0), Y(1)

这意味着:

E[Y(0)|D=1]=E[Y(0)|D=0]E[Y(0)|D=1] = E[Y(0)|D=0]

选择偏误消失了!此时:

E[Y|D=1]E[Y|D=0]观察差异=E[Y(1)Y(0)]ATE\underbrace{E[Y|D=1] - E[Y|D=0]}_{\text{观察差异}} = \underbrace{E[Y(1)-Y(0)]}_{\text{ATE}}

3.5.2 随机实验为什么有效?

  1. 平衡性:随机化保证处理组和对照组在所有可观测和不可观测特征上分布相同
  2. 可忽略性DD的分配不依赖于潜在结果
  3. 无混淆:不存在同时影响DDYY的变量

3.5.3 现实中的挑战

虽然随机实验是”黄金标准”,但现实中往往不可行:

  • 伦理问题:不能随机分配吸烟或不吸烟
  • 成本问题:大规模随机实验可能极其昂贵
  • 可行性:某些处理无法随机分配(如性别、种族)

因此,我们需要观察性研究方法来模拟随机实验的条件。

4 更多”相关≠因果”的陷阱

4.1 常见混淆类型

4.1.1 1. 反向因果(Reverse Causality)

例子:警察越多的地区犯罪率越高

解释:可能是犯罪率高的地区才部署更多警察,而非警察导致犯罪

数学表达YDY \rightarrow D 而非 DYD \rightarrow Y

4.1.2 2. 共同原因(Common Cause)

例子:冰淇淋销量与溺水事故正相关

解释:气温升高同时导致冰淇淋消费增加和游泳活动增加

数学表达ZDZ \rightarrow DZYZ \rightarrow Y,导致Corr(D,Y)0Corr(D,Y) \neq 0即使DDYY无因果效应

4.1.3 3. 选择偏差(Selection Bias)

例子:名校毕业生收入更高

解释:能力强的学生更可能进入名校,也更可能获得高收入

核心问题:样本选择非随机,处理组和对照组不可比

4.2 代码模拟:三种偏误

点击查看三种偏误的模拟代码
np.random.seed(42)
n = 3000

# ========== 场景1:反向因果 ==========
# 真实因果:犯罪率 → 警察数量(而非相反)
crime_rate = np.random.normal(0, 1, n)  # 犯罪率(标准化)
police = 5 + 2 * crime_rate + np.random.normal(0, 0.5, n)  # 警察数量

# 观察到的相关性
observed_corr_reverse = np.corrcoef(police, crime_rate)[0, 1]

# ========== 场景2:共同原因(混淆变量)==========
temperature = np.random.normal(25, 5, n)  # 气温
ice_cream = 100 + 5 * temperature + np.random.normal(0, 10, n)  # 冰淇淋销量
drowning = 2 + 0.3 * temperature + np.random.normal(0, 0.5, n)  # 溺水事故

observed_corr_confound = np.corrcoef(ice_cream, drowning)[0, 1]

# ========== 场景3:选择偏差 ==========
ability = np.random.normal(0, 1, n)  # 能力(不可观测的混淆变量)
# 能力强的人更可能进入名校
elite_school = (ability + np.random.normal(0, 0.5, n) > 0).astype(int)
# 能力强的人收入更高
income = 5000 + 1000 * ability + 500 * elite_school + np.random.normal(0, 500, n)

# 观察到的"名校效应"
elite_income = income[elite_school == 1].mean()
nonelite_income = income[elite_school == 0].mean()
observed_school_effect = elite_income - nonelite_income

print("=" * 60)
print("三种相关≠因果的场景模拟")
print("=" * 60)
print(f"\n【反向因果】警察与犯罪率的相关系数: {observed_corr_reverse:.3f}")
print("   解释:并非警察导致犯罪,而是犯罪多→部署更多警察")

print(f"\n【共同原因】冰淇淋销量与溺水事故的相关系数: {observed_corr_confound:.3f}")
print("   解释:气温同时影响两者,而非冰淇淋导致溺水")

print(f"\n【选择偏差】名校 vs 普通学校收入差异: {observed_school_effect:.0f}元")
print("   解释:部分差异来自能力(能力强的人更可能进名校)")
============================================================
三种相关≠因果的场景模拟
============================================================

【反向因果】警察与犯罪率的相关系数: 0.969
   解释:并非警察导致犯罪,而是犯罪多→部署更多警察

【共同原因】冰淇淋销量与溺水事故的相关系数: 0.886
   解释:气温同时影响两者,而非冰淇淋导致溺水

【选择偏差】名校 vs 普通学校收入差异: 1937元
   解释:部分差异来自能力(能力强的人更可能进名校)

4.2.1 可视化三种偏误

fig, axes = plt.subplots(1, 3, figsize=(14, 4))

# 图1:反向因果
axes[0].scatter(crime_rate, police, alpha=0.3, s=20, color='steelblue')
axes[0].set_xlabel('犯罪率(标准化)', fontsize=11)
axes[0].set_ylabel('警察数量', fontsize=11)
axes[0].set_title('(a) 反向因果:警察 vs 犯罪率\n真实因果:犯罪→警察', fontsize=11, fontweight='bold')
axes[0].grid(alpha=0.3)

# 添加注释
axes[0].annotate('观察到的正相关', xy=(1.5, 8), fontsize=10, color='red',
                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

# 图2:共同原因
axes[1].scatter(ice_cream, drowning, alpha=0.3, s=20, color='coral')
axes[1].set_xlabel('冰淇淋销量', fontsize=11)
axes[1].set_ylabel('溺水事故数', fontsize=11)
axes[1].set_title('(b) 共同原因:冰淇淋 vs 溺水\n共同原因:气温', fontsize=11, fontweight='bold')
axes[1].grid(alpha=0.3)

# 添加注释
axes[1].annotate('虚假的因果关系', xy=(140, 10), fontsize=10, color='red',
                bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))

# 图3:选择偏差
axes[2].scatter(ability, income, c=elite_school, cmap='coolwarm', alpha=0.4, s=20)
axes[2].set_xlabel('能力(标准化)', fontsize=11)
axes[2].set_ylabel('收入', fontsize=11)
axes[2].set_title('(c) 选择偏差:名校 vs 收入\n混淆变量:能力', fontsize=11, fontweight='bold')
axes[2].grid(alpha=0.3)

# 添加图例
from matplotlib.lines import Line2D
legend_elements = [Line2D([0], [0], marker='o', color='w', markerfacecolor='blue',
                          markersize=8, label='普通学校'),
                   Line2D([0], [0], marker='o', color='w', markerfacecolor='red',
                          markersize=8, label='名校')]
axes[2].legend(handles=legend_elements, loc='upper left')

plt.tight_layout()
plt.show()

三种常见偏误的可视化

4.3 关键启示

在观察性研究中,简单比较两组均值是危险的!我们必须考虑:

  1. 处理组和对照组是否可比?
    • 在协变量分布上是否相似?
    • 是否存在系统性差异?
  2. 是否存在混杂变量?
    • 同时影响处理分配和结果的变量
    • 如何识别和测量这些变量?
  3. 如何处理选择偏误?
    • 匹配法:找到可比的子样本
    • 双重差分:利用时间变化
    • 工具变量:找到”准实验”变化
    • 回归控制:统计上调整混淆变量

这正是因果推断方法要解决的问题!

5 课程方法预览

5.1 我们将学习的方法

本课程涵盖两大类方法:

5.1.1 经典因果推断方法(前8周)

这些方法源自经济学和统计学的长期发展:

  1. 匹配法(Matching)
    • 倾向得分匹配(PSM)
    • 精确匹配和共同支持
    • 重叠条件和平衡检验
  2. 双重差分(Difference-in-Differences, DID)
    • 经典DID模型
    • 平行趋势假设
    • 交错DID和动态效应
  3. 工具变量(Instrumental Variables, IV)
    • 两阶段最小二乘法(2SLS)
    • 排他性约束和弱工具变量检验
    • LATE(局部平均处理效应)
  4. 合成控制法(Synthetic Control)
    • 构造合成对照组
    • 安慰剂检验
    • 多期处理

5.1.2 机器学习方法(后6周)

这些方法将ML的预测能力与因果推断结合:

  1. 广义随机森林(Generalized Random Forests)
    • 异质性处理效应估计
    • 置信区间构造
  2. 因果森林(Causal Forests)
    • 发现处理效应的异质性
    • Policy Learning
  3. 双重机器学习(Double Machine Learning)
    • Neyman正交性
    • 高维控制变量
    • 去偏估计

5.2 学习路径

对于每种方法,我们将遵循以下学习路径:

  1. 理解原理
    • 识别假设:什么条件下方法有效?
    • 直观解释:为什么方法有效?
  2. 掌握应用
    • 适用场景:什么时候用?
    • 实施步骤:怎么用?
    • 稳健性检验:结果可靠吗?
  3. 代码实现
    • Python实现(scikit-learn, econml, doubleml)
    • R实现(MatchIt, fixest, grf)
  4. 批判思考
    • 方法局限性:什么情况下不能用?
    • 替代方法:还有其他选择吗?
    • 实际应用:文献中的案例

6 交互式参数探索

6.1 模拟参数的影响

让我们系统探索不同参数对选择偏误的影响:

参数探索代码
def simulate_bias(n_cities=5000, true_drug_effect=-2.0, confound_strength=3.0, threshold=0):
    """
    模拟选择偏误

    Parameters:
    -----------
    n_cities : int
        城市数量
    true_drug_effect : float
        真实药物效果(负值表示缩短康复时间)
    confound_strength : float
        混淆变量的影响强度
    threshold : float
        处理分配阈值(严重程度>threshold获得药物)

    Returns:
    --------
    dict : 包含观察效应、真实效应、选择偏误的字典
    """
    severity = np.random.normal(0, 1, n_cities)
    treatment = (severity > threshold).astype(int)
    recovery = 10 + severity * confound_strength + treatment * true_drug_effect + np.random.normal(0, 1, n_cities)

    data = pd.DataFrame({
        'severity': severity,
        'treatment': treatment,
        'recovery': recovery
    })

    control_mean = data[data['treatment'] == 0]['recovery'].mean()
    treated_mean = data[data['treatment'] == 1]['recovery'].mean()
    observed_effect = treated_mean - control_mean
    selection_bias = observed_effect - true_drug_effect

    return {
        'observed_effect': observed_effect,
        'true_effect': true_drug_effect,
        'selection_bias': selection_bias,
        'treatment_rate': treatment.mean()
    }

# 参数敏感性分析
np.random.seed(42)

print("=" * 70)
print("参数敏感性分析:混淆强度对选择偏误的影响")
print("=" * 70)

confound_values = [0, 1, 2, 3, 4, 5]
results = []

for cs in confound_values:
    np.random.seed(42)  # 固定种子保证可比性
    result = simulate_bias(confound_strength=cs)
    results.append(result)
    print(f"\n混淆强度 = {cs}:")
    print(f"  观察效应: {result['observed_effect']:+.2f}天")
    print(f"  真实效应: {result['true_effect']:+.2f}天")
    print(f"  选择偏误: {result['selection_bias']:+.2f}天")
    print(f"  处理率: {result['treatment_rate']*100:.1f}%")
======================================================================
参数敏感性分析:混淆强度对选择偏误的影响
======================================================================

混淆强度 = 0:
  观察效应: -2.02天
  真实效应: -2.00天
  选择偏误: -0.02天
  处理率: 50.5%

混淆强度 = 1:
  观察效应: -0.43天
  真实效应: -2.00天
  选择偏误: +1.57天
  处理率: 50.5%

混淆强度 = 2:
  观察效应: +1.15天
  真实效应: -2.00天
  选择偏误: +3.15天
  处理率: 50.5%

混淆强度 = 3:
  观察效应: +2.74天
  真实效应: -2.00天
  选择偏误: +4.74天
  处理率: 50.5%

混淆强度 = 4:
  观察效应: +4.33天
  真实效应: -2.00天
  选择偏误: +6.33天
  处理率: 50.5%

混淆强度 = 5:
  观察效应: +5.92天
  真实效应: -2.00天
  选择偏误: +7.92天
  处理率: 50.5%

6.1.1 结果解读

关键发现

  • confound_strength=0时,无选择偏误,观察效应=真实效应
  • 随着混淆强度增加,选择偏误急剧增大
  • 即使真实效应为负(药物有效),强混淆可能导致观察效应为正(看似无效甚至有害)

6.2 可视化参数影响

fig, ax = plt.subplots(figsize=(10, 6))

confound_values = [0, 1, 2, 3, 4, 5]
observed_effects = [r['observed_effect'] for r in results]
selection_biases = [r['selection_bias'] for r in results]

ax.plot(confound_values, observed_effects, 'o-', linewidth=2, markersize=8,
        label='观察效应', color='coral')
ax.axhline(y=-2, color='green', linestyle='--', linewidth=2, label='真实效应 (-2天)')
ax.plot(confound_values, selection_biases, 's-', linewidth=2, markersize=8,
        label='选择偏误', color='red', alpha=0.7)

ax.set_xlabel('混淆强度(严重程度对康复时间的影响)', fontsize=12)
ax.set_ylabel('天数', fontsize=12)
ax.set_title('混淆强度如何影响观察结果', fontsize=14, fontweight='bold')
ax.legend(fontsize=11)
ax.grid(alpha=0.3)
ax.axhline(y=0, color='black', linewidth=0.5)

plt.tight_layout()
plt.show()

混淆强度对选择偏误的影响

政策含义

  • 在混淆变量影响强的领域(如医疗、教育),简单比较极其危险
  • 必须采用因果推断方法才能获得有效结论
  • 干预前评估混淆变量强度是研究设计的关键步骤

7 总结

7.1 本讲要点回顾

7.1.1 1. 课程定位

  • 机器学习:关注预测(是什么)
  • 因果推断:关注归因(为什么)
  • 结合:AI时代的科学决策能力

7.1.2 2. 核心问题:相关性 ≠ 因果性

  • 混淆变量:同时影响处理和结果
  • 选择偏误:处理组和对照组不可比
  • 反向因果:结果影响处理分配

7.1.3 3. 分析框架:潜在结果模型

  • 每个个体有两种”潜在”状态
  • 因果推断的基本问题:无法同时观测
  • 随机实验:通过随机化消除选择偏误

7.1.4 4. 学习内容概览

  • 经典方法:匹配、DID、IV、合成控制
  • 前沿方法:因果森林、双重机器学习
  • 实践应用:真实案例分析

7.2 关键公式汇总

概念 公式 说明
个体处理效应 τi=Yi(1)Yi(0)\tau_i = Y_i(1) - Y_i(0) 不可观测
平均处理效应 ATE=E[Y(1)Y(0)]\text{ATE} = E[Y(1) - Y(0)] 目标参数
观察差异分解 E[YD=1]E[YD=0]=ATT+选择偏误E[Y \mid D=1] - E[Y \mid D=0] = \text{ATT} + \text{选择偏误} 偏误来源
随机化条件 DY(0),Y(1)D \perp\!\!\perp Y(0), Y(1) 无偏估计

7.3 下一讲预告

第二讲:因果推断基础——潜在结果框架深入

  • 条件独立假设(CIA)
  • 重叠条件(Overlap)
  • 识别 vs 估计
  • 反事实预测的机器学习方法

7.4 课后思考

  1. 在你的研究领域,找到一个”相关≠因果”的例子。混淆变量可能是什么?

  2. 随机实验的局限:为什么有些重要问题无法通过随机实验回答?观察性研究的价值是什么?

  3. AI与因果推断:大语言模型能帮助我们做因果推断吗?其局限性在哪里?


联系方式

  • 邮箱:chenzhiyuan@rmbs.ruc.edu.cn
  • 办公室:919
  • Office Hours:周四下午2-3点

本讲义基于 Scott Cunningham《Causal Inference: The Mixtape》、Angrist & Pischke《Mostly Harmless Econometrics》以及 Chernozhukov 等的《Applied Causal Inference Powered by ML and AI》整理而成。