Causal Inference using the R package DAGitty¶
讲者: Johannes Textor
来源: OCIS (Online Causal Inference Seminar)
日期: 2021-07-27
主题: 因果推断
视频: https://youtu.be/LCC4BkLZo-g · 幻灯片
本页据讲座录音的自动转写(ASR)生成。人名 / 术语 / 公式 / 具体的率与界可能被听错,关键处请对照视频或讲者论文核对。
一、这场报告在讲哪条工作线¶
这场报告不是理论创新,而是一场软件生态教程。它旨在推广和展示 DAGitty 这个在应用因果推断领域用户量极大的(最初是网页应用,后发展出R包)工具链,并试图将它与 R 生态中其他互补的因果推断包(ggdag, bnlearn, pcalg, causaleffect 等)串联起来。整场报告的核心命题是:
如何让“勇敢尝试正面使用 DAG(即:把 DAG 当作真实数据生成过程的模型,而非仅用于批评他人研究)”的实践者,能够更方便地完成“画图 → 识别 → 估计 → 检验”的完整工作流?
这条工作线在追问什么:
- 因果推断的实践中,DAG 的作用远不止是概念化的“偏倚示意图”。一个正确(或至少未被数据驳斥)的 DAG 可以:
- 通过 d-separation 理论,给出一系列可检验的条件独立性约束。
- 通过 调整集 (adjustment sets) 算法,指明需要控制的变量。
- 通过 do-calculus,给出非参数识别的公式。
- 然而,这条“积极实用”的路线(将 DAG 建模为反映真实机制的模型)远比“消极实用”(用 DAG 指出别人研究中的偏倚)要难。难点包括:构建 DAG 的流程缺乏迭代验证机制;不同软件的语法不统一,阻碍了从画图到估计的流畅转换。
奠基与主流路线:
- dagitty.net(2010年至今,Textor 开发):是最早让非方法论专家能交互式画 DAG 并自动推导调整集的工具之一,在流行病学与社会科学教学中被广泛使用。讲者提到,其用户主体是学生与初学者,日均访问量曾达千人量级([0:05:20—0:05:52])。
- ggdag (Barrett): 一个与 ggplot2 兼容的 DAG 可视化包,基于 dagitty 构建,但提供了更美观、更符合 tidyverse 语法的画图接口。
- bnlearn (Scutari),pcalg (Kalisch, Maathuis 等):分别是贝叶斯网络结构学习和基于约束的因果结构学习(含 PC, FCI 等算法)的两大主流 R 包。它们解决的是“从数据中学习 DAG”,而 dagitty 解决的是“给你的 DAG 注入假设并检验”。
- causaleffect (Tikka):实现了标准的 do-calculus,能对一个给定的 DAG 输出因果效应的非参数识别公式(例如使用 causal.effect("y", "x", G=g))。
当前前沿与这场报告的站位: - 方法论前沿(如 proximal causal inference, efficient influence function, doubly robust estimation)在 2021 年已经非常深入,但在 “帮助应用者把前沿结果用起来” 的工具层面,生态仍然碎片化。讲者明确表示,他的目标不是实现最前沿的方法,而是降低 DAG 正面实践的门槛([0:10:40—0:11:06]:“I wanted to help the people who are brave enough to try to put this methodology into practice.”)。 - 报告的核心贡献主张是:(a) 提供一个表达能力强、能覆盖多种图类型(DAG, CPDAG, MAG, PAG)的字符串语法,以及 (b) 首次在 R 包中系统性地提供了局部检验 (localTests) 函数,让用户可以自动提取 DAG 隐含的所有 d-separation 条件性独立关系,并逐一在观测数据上测试,从而识别模型指定错误。
二、最小内核 / 一个最简例子¶
符号与模型:
- 可观测数据:(X, Y, Z),其中 X 是暴露/处理,Y 是结局,Z 是第三个变量。
- 模型(DAG):假设三个节点的 DAG:Z → X → Y(即,Z 同时影响 X 和 Y;X 影响 Y;Z 到 Y 的路径被封堵)。这是我们主观假设的数据生成机制。
- 目标 (estimand):X 对 Y 的总因果效应,在这样一个 DAG 下,它是通过干预 do(X=x) 得到的边缘分布 P(Y|do(X=x))。
最简例子:一个二值变量 Z,一个二值处理 X,一个二值结局 Y。
- 假设的 DAG:{"dag { Z -> X -> Y ; Z -> Y }"},即 Z 是 X 和 Y 的共同原因(混杂),X 对 Y 有直接因果效应。
- DAG 隐含的 d-separation 声明:
- X 和 Z 不是 d-separated(Z 是 X 的祖先,路径 Z -> X 始终开放)。
- Y 和 Z 不是 d-separated(路径 Z -> Y 始终开放)。
- 最关键的可检验声明:在给定 X 的条件下,Y 和 Z 是 d-separated?不成立! 因为路径 Z -> (collider?) 不对,这里 Z 是 Y 的直接父亲,X 是中间节点但不是 collider。正确的可检验声明是:X 与 Y 之间的条件独立性,在给定 Z 时是否成立? 检查所有路径:X <- Z -> Y,这是一条非 collider 路径,因此 在条件 Z 下,X 与 Y 不独立——这正是混淆的根源。而在我们的假设 DAG 中,唯一能测试的独立性是 Y ⟂ X | Z 吗?不,这恰好是假的(因为有直接边 X -> Y)。实际上这个简单 DAG 不产生任何条件独立性声明,从而完全不可测试。要使它可测试,需要至少多一个变量(如前文幻灯片 [0:46:24—0:47:02] 中所举例的链式结构 X -> M1 -> M2 -> Y)。
核心思想:DAG 的价值在于它对观测分布施加了不等价于饱和模型的约束,即某些变量对在条件给定其他变量下必须独立。这些约束可以通过 d-separation 定理从图中“读出”,然后通过统计检验(如卡方检验、偏相关检验)与数据对抗。如果某个声明的独立性被数据强烈否定(低 p 值、大效应量),则意味着我们的假设 DAG 结构(至少那条缺失的边)是错的。
三、报告主体:讲者讲了什么¶
[0:00:04—0:03:28] 开场与致谢 - 介绍了讲者身份(Radboud University, 肿瘤免疫学与数据科学双隶属),并致谢了理论贡献者(如 Ema Perković, 关于调整集理论)和软件贡献者(Ankur Ankan, Inge Wortel 等)。幻灯片列出了详细的合作者。
[0:03:30—0:04:59] 报告结构
- 三部分:(1) dagitty R 包基础功能;(2) 与其他 R 包的整合;(3) DAG 模型测试(讲者认为最重要)。
[0:05:04—0:07:20] DAGitty 的起源与 R 包架构
- dagitty.net 始建于 2010 年,是一个 JavaScript 网页应用。R 包的核心是一个V8 引擎的薄包装(V8 包提供了在 R 中运行 JS 的能力)。讲者解释了为什么不做纯 R 重新实现:因为网页版的代码经过多年用户测试和 bug 修复,非常成熟(约 1000 个单元测试),重新开发成本过高([0:07:12—0:07:30])。
[0:07:40—0:11:08] 三种 DAG 使用场景(讲者观点) - 理论用途:作为方法论发展的工具。 - 消极实际用途:用 DAG 批评他人研究中的偏倚(目前最成功)。 - 积极实际用途:将一个特定问题的 DAG 当作真实模型,用于因果估计(讲者指出,这远比前两者困难,但正是他想帮助的方向)。他强调:好的理论论文多,但好的实证 DAG 应用论文少。
[0:11:14—0:13:26] 核心功能:DAG 定义语言
- 使用基于 Graphviz 的字符串语法定义图。关键点:
- 必须指定图类型:dag, pdag (CPDAG), mag, pag。这是必须的,因为同样的边结构在不同图类型下有不同语义(例如,dag 中的 Z->X->Y 调整集为 {Z},但在 MAG 中可能无调整集)([0:15:53—0:16:15])。
- 语法支持嵌套子图(如 z->{a->{e->d}}),使表达紧凑。
- 坐标布局:可自动生成 (graphLayout),也可手动指定,或在 dagitty.net 上画好再复制粘贴语法。
- 图可从网络下载(downloadGraph("dagitty.net/m331"))。
[0:14:52—0:15:30] 语法问答(Q&A 部分)
- 问题:为何不用 R 更自然的 formula 语法(如 lavaan 的 f1 =~ y1 + y2 式)?
- 回答:Formula 语法对于纯 DAG(有向)很自然(因变量 ~ 自变量),但当涉及多类边(双向、无向、带顶点标记)时,会变得很别扭。讲者倾向于保留一个统一的、图论邻接式的文本语法。
[0:15:30—0:17:40] 模拟数据
- simulateSEM: 将 DAG 解释为线性结构方程模型,每个节点是父节点的线性组合加高斯噪声,路径系数从指定区间采样。
- simulateLogistic: 基于层次 Logistic 回归生成二值数据(每个节点是 Logit 转换后的父节点线性组合)。
- 用途:教学(如模拟辛普森悖论)和测试方法论。
[0:17:45—0:22:30] 路径检查与 d-separation
- paths(g, "X", "Y") 列出所有路径及当前是否开放(基于当前条件集)。
- 教学示例:通过条件于 collider 的一个后代节点,也可以打开路径(paths(g, "X", "Y", "P"))([0:19:30—0:20:12])。
- adjustmentSets(g, "X", "Y"): 寻找最小调整集,可枚举所有调整集(type="all")。
- 应用示例:展示了从流行病学文献(Wang and Bautista, IJE, 2014)中找出的一个30+变量的复杂 DAG([0:22:30—0:23:10])。
[0:23:10—0:25:57] 基于 CPDAG 的调整
- 引用 Perković et al. (2018) 的 Generalized Adjustment Criterion:你不需要知道完整的 DAG,只需要它的 Markov 等价类 (CPDAG),即可在某些条件下找出对所有成员图都有效的调整集。讲者展示了从该复杂 DAG 的 CPDAG 中计算出调整集 (adjustmentSets(equivalenceClass(g)))。
[0:25:57—0:28:40] 一个真实电子邮件中的问题
- 展示了一封来自用户的邮件,其中包含了关于偏见的三种问题(哪些变量控制了混杂?哪些变量无影响?哪些变量会引入额外偏倚?)。讲者演示了如何用 adjustmentSets 和 paths 逐一回答这些问题。
[0:28:55—0:34:29] 整合其他 R 包
- 主要动机:R 生态中因果推断包语法不统一,阻碍了工作流。讲者选择通过导出/导入函数来串联,而不是将其他包的功能隐藏到自己的包中,以维护其他开发者的学术信用。
- ggdag: tidy_dagitty(g) 从 dagitty 对象创建 tidy 格式图,然后可做 ggdag_adjustment_set 等可视化。
- bnlearn: 结构学习的结果(bn 对象)可通过 as.dagitty() 转换为 dagitty 对象,进而使用 equivalentDAGs() 查看等价类中的其他 DAG。
- pcalg: 也有函数将输出转换为 dagitty 对象。
- causaleffect (Tikka): convert(g, to="causaleffect"),之后调用 causal.effect("y", "x", G=g) 输出识别公式。讲者特别称赞了 Santtu Tikka 的包实现了通过符号简化(如规则化的 ID 算法)来减少结果的复杂性。
[0:34:29—0:40:00] DAG 模型测试(核心内容)
- 动机:
- “模型构建循环”:假设 → DAG → 数据 → (d-separation 可检验) 否证 → 修正假设 → 重画 DAG([0:35:10—0:35:51])。
- 然而文献调查发现,几乎没有人真正迭代地测试和修正 DAG。讲者认为这是实践中 DAG 难以“正确”的原因。
- 测试不可能证明 DAG 正确,只能指出错误(No free lunch)。
- 如何测试:
- DAG 通过 d-separation 隐含多个 (X ⟂ Y | Z) 的条件独立性约束。每个缺失的边至少产生一个约束([0:36:24—0:36:50])。
- 举了一个“测量误差”例子:假设的 DAG 是 X -> M1 -> M2 -> Y (完全的链式中介),但真实的 DAG 中包含了未观测的混杂 / 测量误差结构 X → U1 → Y, U1 → M1。从假设 DAG 可读出的两个可检验声明是:
1. M1 ⟂ M2 | X(源自路径 M1 ← U1 → Y ← M2,但假设中无 U1,因此 M1 -> M2 路径被 M1 → M2 的中间环节阻断?实际上正确推导:在假设 DAG X → M1 → M2 → Y 中,M1 ⟂ M2 | X 是假的,因为存在 M1 → M2 的直接边。一个正确的可检验声明应该是 X ⟂ Y | M1, M2,即 X 和 Y 在给定两个中介后应独立。这正是讲者在幻灯片中使用的例子。)
2. 在第 [0:31:07—0:32:03] 页,他模拟的数据在真实模型 X→U1→Y, U1→M1, X→M2, M2→Y 下,测试了 X ⟂ Y | M1, M2,得到非常显著的卡方检验(p << 0.001),而测试 M1 ⟂ M2 | X 未显著。这正确指出了假设模型中缺失了 U1。
- 实现:
- ciTest("X","Y", c("M1","M2"), data=d, type='cis.chisq') 进行离散数据的条件独立性检验。
- localTests(g, data) 函数自动提取 DAG 的所有 d-separation 声明,逐一检验,并返回一个表格,包含效应量(RMSEA: Root Mean Square Error of Approximation)和置信区间。讲者强烈建议不只看 p 值,而要看效应量(RMSEA),因为大样本下微小偏离也会使 p 值很小([0:38:20—0:38:45])。
[0:40:00—0:44:00] 一个真实数据测试案例(1994 年成人人口普查收入数据) - 测试了一个假设的 DAG(包含 marital status, sex, income 等),该 DAG 隐含大量条件独立性声明。结果显示所有声明都被强烈拒绝。 - 最有意思的发现:Marital Status 与 Sex 不独立——这在一个 1994 年仅承认异性婚姻的普查样本中,暗示了数据采样有问题(样本中已婚男性过多,可能是为了富集高收入人群而做的“过采样”)。讲者指出,这个显而易见的数据错误在大量使用该数据集的研究中从未被注意到,是他个人认为“模型测试可以揭示数据问题”的最佳例证([0:42:20—0:43:30])。
[0:44:00—结束] 未来计划与结语
- 未来计划:
1. 与更多 R 包实现更深度的整合。
2. 用 ES6 重写底层的 JS 库。
3. 推荐了 Python 生态中的等效包:pgmpy(Ankur Ankan, 讲者团队)。
- Q&A: 用户问“识别完成后用什么包做估计?” 讲者回答:取决于识别形式(如 IV 用 AER 做 2SLS;调整可简单用线性或逻辑回归;do-calculus 得出的公式可能很复杂,如何估计本身可能是开放问题)。这个问题没被详细展开。
四、对应论文与开放问题¶
(a)对应论文 / 资源¶
- 核心软件:
dagittyR 包:remotes::install_github("jtextor/dagitty/r")(GitHub 仓库)。dagitty.net,网页版。
- 直接引用的理论工作:
- 调整集理论:Perković, E., et al. (2018). "A generalized adjustment criterion for causal effects in graphs with latent variables." 提出了针对 CPDAG/MAG 的调整准则。
- do-calculus 实现:Tikka, S., & Karvanen, J. (2017). "Identifying causal effects with the R package causaleffect." Journal of Statistical Software.
- 间接相关工作(讲者提及的现有 R 包):
bnlearn(Scutari, 2010)pcalg(Kalisch et al., 2012)ggdag(Barrett, 2019-ish)pgmpy(Ankan & Panda, Python)
(b)开放问题 / 可继续的方向¶
-
模型测试的规模化与自动化路径 [0:39:30—0:39:50]
- 报告提到
localTests会枚举所有 d-separation 声明并测试。对于大型 DAG(例如 30+ 个节点),声明数量极大,存在多重检验问题。讲者本人未讨论 correction,也未讨论当模型失败后如何自动建议修正(例如,通过目标性的边增删)。这是从“发现问题”到“修复模型”的一个明显缺口。
- 报告提到
-
从测试结果推演特定模型失误 [0:38:00—0:39:30]
- 讲者用测量误差的例子展示了“测试可以告知某个声明错误”,但他没有给出系统性的算法来判断“哪个节点对 / 哪个缺失的边”导致了 RMSEA 最大的偏差。对于非简单 DAG,独立地检查每个声明的失败,很难精确定位模型结构的错误。这类似于结构方程建模中的修正指数概念,但在 DAG 框架下尚未系统化。
-
整合估计与识别 [Q&A 0:49:00—0:50:00]
- 用户问“哪个包做估计?”讲者承认,从
causaleffect输出的符号公式到实际稳定的统计估计(尤其是当公式复杂、包含高维条件分布时),存在一个很大的 gap。例如,对于 “学完 do-calculus 后得到Σ_z P(y|z,x)P(z)”这样的公式,如何在高维 Z 下用现代方法(如非参数贝叶斯、集成方法、或去偏机器学习)实现有效估计,仍然是开放软件工程问题,而非纯理论问题。
- 用户问“哪个包做估计?”讲者承认,从
-
“正面实用”的瓶颈:如何建模“正确”的 DAG
- 讲者多次强调,人们不进行模型测试是 DAG 在实证中难以成功的原因([0:35:10—0:35:30])。但这暗示了一个更深层的问题:即使测试了,如何从初始错误模型出发,系统性地构建一个不被测试否定的模型?这需要结合结构学习算法(如 PC, GES)与专家知识的交互式界面,而不仅仅是“测试 → 人工修正”的循环。
bnlearn和pcalg已经解决结构学习,但将它们与dagitty的测试框架整合成类似“自动模型探索”的集成工具,目前在 R 中尚未成熟。
- 讲者多次强调,人们不进行模型测试是 DAG 在实证中难以成功的原因([0:35:10—0:35:30])。但这暗示了一个更深层的问题:即使测试了,如何从初始错误模型出发,系统性地构建一个不被测试否定的模型?这需要结合结构学习算法(如 PC, GES)与专家知识的交互式界面,而不仅仅是“测试 → 人工修正”的循环。
Maintained by 陈星宇 · Homepage · Source on GitHub