对撞机:控制它,会凭空造出相关
如果这本书只能留一章,我留这一章。因为混淆你大概已经知道,而对撞机会让你重新审视自己做过的每一次「我们控制了这些变量」。
你在分析用户数据,想知道「用了功能 A」对留存有没有帮助。同事建议:「把用户付费金额也加进回归控制一下,毕竟付费用户和免费用户很不一样。」假设真实结构是:用功能 A 会促进付费,留存高的人也更可能付费。这个建议?
注意题干给了结构:功能 A → 付费,留存 → 付费。请照着上一章的三个零件对号入座。
亲手拧一次
两行独立的随机数,A 和 C。然后 B := A + C + 噪声。B 是它们的共同后果——一个对撞机。
三个按钮对应三种「控制 B」的方式,请依次点:
- 全部人:r(A, C) = −0.004。两万个样本剩下的抽样零头,实质是零。
- 只看 B > 1.0 的人:散点图整个歪掉,明显的负相关。
- 在回归里控制 B:偏相关 −0.501。
请盯着第一步和第三步的差距看一会儿:−0.004 → −0.501。生成 A 和 C 的代码没有变,数据没有变,变的只是你在分析时多写了一个变量名。
当 A 和 C 共同影响 B 时,条件化 B(不管用什么方式)会在 A 和 C 之间制造出一个本不存在的关联。
直觉:B 的值被固定住之后,A 和 C 必须「凑」出这个值。一个大了,另一个就得小。它们被绑在了一起,而绑它们的绳子是你自己系上去的。
「控制」的三种伪装
这一节是本章的实用价值所在。因为「条件化」在实践中长着三张完全不同的脸,其中两张不像「控制」。
伪装一:把它放进回归
最明显的一种。lm(y ~ x + z) 里的 z,就是在条件化 z。这是唯一一种你会主动意识到「我控制了它」的形式。
伪装二:分组看 / 只看某一类
「我们只分析付费用户」「我们只看 DAU 大于 1000 的 App」「我们把用户分成高活跃和低活跃分别看」——全都是条件化。
这一种最容易漏,因为它不发生在建模阶段,发生在写 SQL 的 WHERE 子句里。
# 这一行就是条件化 df = df[df.paid_amount > 0] # ← 如果 paid_amount 是对撞机,你已经中招了
伪装三:数据集本身
最隐蔽的一种。第 4 章的返航飞机、名校学生、活跃用户——数据是怎么进到你手上的,本身就是一次条件化,而且是在你拿到数据之前就完成的。
这一种没有代码可以指着看。发现它的唯一办法是问一句:「这批数据是被什么筛出来的?那个筛子和我要研究的东西有关系吗?」
对撞机偏差还有一条扩展规则,它比主规则更容易害人:
控制对撞机的任何一个后代,效果和控制对撞机本身一样。
也就是说,你不需要真的控制 B,只要控制了任何一个「受 B 影响」的东西,那条路照样会通。因为知道 B 的后代,就等于知道了一部分关于 B 的信息。
实践上这条规则的推论非常锋利:凡是在处理之后才发生的变量,都不要放进回归。不管它看起来多像一个该控制的东西。因为「处理之后才发生」意味着它可能是处理的后代、结果的后代、或者两者的共同后代——三种情况里有两种会害你。
为什么这件事在现实中特别难防
因为对撞机看起来像一个该控制的变量。
回到开头那道题的结构:功能 A → 付费,留存 → 付费。在数据里你会观察到:
- 付费金额和「用了功能 A」相关 ✓
- 付费金额和留存相关 ✓
这正是教科书上「混淆变量」的经验判据——「同时和处理、和结果相关」。而在这里,它是对撞机,控制它会毁掉估计。
C → X, C → Y(混淆)和 X → S, Y → S(对撞)这两个结构,在数据里都表现为「这个变量和处理、和结果都相关」。
光看相关矩阵,它们长得一模一样。区分它们的唯一依据是时序和机制——哪个在前,哪个在后,谁导致谁。
这就是为什么第 8 章之后,我们必须画图。图不是为了好看,图是唯一装得下这个区别的容器。
Berkson 悖论(1946):住院病人中,两种毫不相干的疾病之间会呈现负相关。因为「住院」是一道门——你得病得够重才会住院,得了 A 病就不需要 B 病也很重才能进来。约瑟夫·伯克森当年正是从医院数据里发现这个的。
「肥胖悖论」:在心衰患者中,肥胖者的存活率反而更高。这个反直觉的结论有一部分可能来自对撞机——「得心衰」这件事本身是一道门,瘦的人得心衰往往意味着有别的更严重的问题。这个解释在流行病学界仍有争议,本书不下结论,只指出它是一个候选机制。
招聘中的「学历 vs 能力」:第 4 章说过。你的筛简历规则就是那道门。
推荐系统的反馈回路:你只能观察到「被推荐了的内容」的点击数据。「被推荐」是一道门,而它同时受内容特征和用户特征影响。这让离线评估系统性地偏向现有策略——这也是推荐系统里「离线指标涨、线上不涨」的一个来源。
这条错误的经验判据在很多统计课本和实践指南里都出现过,甚至有一个名字叫「变量选择的相关性准则」。它在只有混淆的简单世界里没问题,一旦图里有对撞机就会翻车。
正确的判据是第 10 章的后门准则,它的第一条就是:调整集里不能有处理的后代。这一条直接把对撞机和中介一起排除掉了。
C坏建议,这会引入偏差。
题干给的结构是 功能A → 付费 ← 留存——付费金额是一个标准的对撞机。控制它会在「用了功能 A」和「留存」之间造出一个假关系,而且这个假关系的方向通常是负的,可能把一个正效应压成零甚至负数。
这一章的一句话
控制一个共同后果,会在两个原因之间凭空造出关系;而「控制」有三张脸——回归里的一列、SQL 的 WHERE、和数据集本身。
下一章把前两章的规则变成一台机器。你会拿到一个只看图、不看数据的判定器:输入两个变量和一个条件集,它告诉你它们在这张图下独立不独立。这台判定器和 Python 的 networkx 在 1484 个随机图查询上逐题对过答案,一题不差。