结构因果模型与do-演算速成
在数据分析的世界里,我们经常看到变量之间的相关性,但如何确定它们是否存在真正的因果关系?本文将通过两个经典案例——“冰淇淋-气温-溺水"与"吸烟-焦油-肺癌”,用有向无环图(DAG)直观展示变量间的因果路径,并深入讲解Judea Pearl的三条do-演算规则,演示如何从观测分布P(Y|X)推导到干预分布P(Y|do(X))。同时,我们将对比背门调整、前门调整和工具变量三种常见识别方法,并用Python/DoWhy库实现完整推断流程,为后续的潜在结果与倾向得分分析奠定基础。
一、DAG基础与因果路径
1.1 DAG的定义与要素
有向无环图(DAG) 是一种表示变量间因果关系的图形化工具,由节点和有向边组成,具有以下特点:
- 节点:代表变量,如"冰淇淋"、“气温”、"溺水"等
- 边:表示变量间的因果关系,箭头方向表示因果方向
- 无环:不存在循环路径,确保因果关系的单向性
DAG中存在三种基本结构模式:
- 链式结构(Chains):X → Y → Z,表示X通过Y影响Z
- 叉式结构(Forks):X ← Y → Z,表示Y是X和Z的共同原因
- 对撞结构(Colliders):X → Y ← Z,表示Y是X和Z的共同结果
1.2 冰淇淋-溺水案例的DAG
冰淇淋消费量与溺水事件之间存在明显的正相关关系,但这是否意味着冰淇淋会导致溺水呢?
import networkx as nx
import matplotlib.pyplot as plt
# 创建冰淇淋-溺水DAG
G1 = nx.DiGraph()
G1.add_nodes_from(['冰淇淋', '气温', '溺水'])
G1.add_edges_from([('气温', '冰淇淋'), ('气温', '溺水')])
# 绘制图
pos = nx.spring_layout(G1)
nx.draw(G1, pos, with_labels=True, node_color='lightblue', node_size=3000, arrowsize=20)
plt.title('冰淇淋-溺水因果图')
plt.show()
1.3 吸烟-肺癌案例的DAG
吸烟与肺癌之间也存在正相关关系,但这种相关性是否意味着吸烟导致肺癌?
# 创建吸烟-肺癌DAG
G2 = nx.DiGraph()
G2.add_nodes_from(['吸烟', '焦油', '肺癌'])
G2.add_edges_from([('吸烟', '焦油'), ('焦油', '肺癌')])
# 绘制图
pos = nx.spring_layout(G2)
nx.draw(G2, pos, with_labels=True, node_color='lightblue', node_size=3000, arrowsize=20)
plt.title('吸烟-肺癌因果图')
plt.show()
二、d-分离原理与因果路径阻断
2.1 d-分离的定义
d-分离(D-separation) 是一种判断变量间条件独立性的图形化方法,适用于DAG。其基本原理是:
- 若存在一条路径,但该路径被某个结点阻断,则称该路径为非活跃路径(inactive trail)
- 若两个变量之间的所有路径都被阻断,则这两个变量在给定条件下是条件独立的
判断路径是否被阻断的三种情况:
- 尾-尾结构:X ← Z → Y
- 若不以Z为条件,则X与Y相关
- 若以Z为条件,则X与Y独立
- 头-尾结构:X → Z → Y
- 若不以Z为条件,则X与Y相关
- 若以Z为条件,则X与Y独立
- 头-头结构:X ← Z → Y
- 若不以Z为条件,则X与Y独立
- 若以Z为条件,则X与Y相关
2.2 d-分离动画演示
以下动画演示了冰淇淋-溺水案例中,当控制"气温"变量时,如何阻断"冰淇淋"与"溺水"之间的关联路径:
import matplotlib.animation as animation
from IPython.display import HTML
# 创建图并设置初始状态
G1 = nx.DiGraph()
G1.add_nodes_from(['冰淇淋', '气温', '溺水'])
G1.add_edges_from([('气温', '冰淇淋'), ('气温', '溺水')])
pos = nx.spring_layout(G1)
node_colors = ['lightblue'] * 3
edge_colors = ['black'] * 2
# 创建动画函数
def update(frame):
plt.clf()
if frame == 0:
# 初始状态
nx.draw(G1, pos, with_labels=True, node_color=node_colors, edge_color=edge_colors)
plt.title('初始状态')
elif frame == 1:
# 控制"气温"变量
node_colors = ['lightblue', 'red', 'lightblue']
nx.draw(G1, pos, with_labels=True, node_color=node_colors, edge_color=edge_colors)
plt.title('控制气温变量')
elif frame == 2:
# 阻断路径
edge_colors = ['black', 'red']
nx.draw(G1, pos, with_labels=True, node_color=node_colors, edge_color=edge_colors)
plt.title('阻断路径')
# 创建动画
fig = plt.figure(figsize=(8, 6))
ani = animation.FuncAnimation(fig, update, frames=3, interval=1000)
HTML(ani.to_html5_video())
三、Judea Pearl的三条do-演算规则详解
do-演算是处理干预的数学工具,由Judea Pearl提出,用于从观测分布P(Y|X)推导干预分布P(Y|do(X))。
3.1 规则表格:公式与口语解释
规则名称 | 公式 | 口语解释 |
---|---|---|
规则1:无关变量增删 | P(Y | do(X),Z,W) = P(Y |
规则2:干预与观察的等价性 | P(Y | do(X),Z) = P(Y |
规则3:因果路径不存在时的干预 | P(Y | do(X)) = P(Y) 如果从X到Y没有因果路径 |
3.2 冰淇淋案例:推导P(溺水|do(冰淇淋))
在冰淇淋-溺水案例中,"气温"是混杂因子,我们需要通过do-演算推导出干预分布P(溺水|do(冰淇淋)):
- 初始分布:P(溺水|冰淇淋)
- 应用规则2:由于"气温"阻