ggdag:DAG和因果图
近几年来,因果推断同时受到多个学科的重视,是最火热的研究方向之一。因果图(或称路径图)是研究因果关系的一个有效的辅助性工具。借助因果图可以分析因果关系,将复杂问题图形化。本文介绍一个用来绘制因果图的R包ggdag。
1、DAG
有向无环图(DAG,Direct Acyclic Graph)是一种图模型。图由节点和边组成,节点代表变量,边代表变量之间的某种联系。如果两个节点之间的边是有明确指向的,就是有向图。如果一个有向图从某个节点出发后,无法通过若干条边回到原节点,就是一个有向无环图。贝叶斯网是有向无环图的一个著名的例子。
2、因果图
DAG可以用作因果图。在DAG中,因果关系由变量之间的有向边表示(方向箭头由因指向果)。在因果推断的背景下,DAG表明(1)两个变量之间的因果关系必须是单向的,变量之间不能相互影响。(2)要完成DAG,必须包含DAG中任何两个变量的共同原因。
三种路径
路径是指连接两个节点的一系列有向边,这些边的方向不一定相同,但是只能通过一个变量一次。
路径可分为三类:因果路径(或链状路径,A—>B—>C)、混淆路径(或叉状路径,A<—C—>B)和对撞路径(或反叉状路径,A—>B<—C)。
(1)因果路径
因果路径(也叫有向路径)是解释变量(也称为处置、暴露)X指向被解释变量(也称为结果)Y的单向路径。
图1-a 直接因果关系路径,X和Y之间没有中介变量(mediator)
图1-b 间接因果关系路径,X和Y之间有中介变量A
图1-c 两种情况的混合。
如果两个 变量之间存在着因果关系路径,它们就存在相关性,所以因果路径为开放路径。
(2)混淆路径
如果在X和Y的路径上存在一个变量C同时影响X和Y,这个变量C叫做混淆变量(confounder),这条路径称为混淆路径(也叫后门路径)。
图2-a C是X和Y共同的解释变量,C是一个混淆变量。
图2-b C是观测不到的混淆变量
(3)对撞变量
如果变量D被变量X和Y共同影响,D叫做对撞变量(collider)。包含对撞变量的路径是对撞路径(也叫阻塞路径)。
图3 对撞变量
对撞路径不会造成X和Y之间的相关性,是一条死路径。
3、ggdag
dagitty(有个对应的R包,也叫dagitty)是一个功能强大的基于浏览器的因果模型图分析工具。ggdag对dagitty包做了一些扩展,可以创建整洁(tidy)的因果有向无环图。ggdag的图形建立在ggplot2和ggraph(见网络数据可视化(3修订版)ggraph:ggplot2的网络可视化)的基础上,可以在R环境下更轻松的绘制DAG。
(1)创建DAG
ggdag可以直接将dagitty包创建的DAG转化为整洁的数据结构。
library(dagitty)library(ggdag)
dag <- dagitty('dag{y <- z -> x}')tidy_dagitty(dag)#> # A DAG with 3 nodes and 2 edges#> ##> # A tibble: 4 x 8#> name x y direction to xend yend circular#>#> 1 z 28.5 23.3 -> x 28.5 24.4 FALSE #> 2 z 28.5 23.3 -> y 28.5 22.1 FALSE #> 3 x 28.5 24.4NA NA FALSE #> 4 y 28.5 22.1NA NA FALSE
(注:dagitty支持很多图模型。ggdag目前只支持DAG。)
ggdag提供了使用R的公式语法创建dagitty对象的功能,通过dagify()函数实现。dagify()接受任意数量的公式来创建DAG。它还可以选择声明变量,以及每个节点的坐标和标签。
dagified <- dagify(x ~ z, y ~ z, exposure = 'x', outcome = 'y')tidy_dagitty(dagified)#> # A DAG with 3 nodes and 2 edges#> ##> # Exposure: x#> # Outcome: y#> ##> # A tibble: 4 x 8#> name x y direction to xend yend circular#> #> 1 z 23.6 20.4 -> x 23.6 21.5 FALSE #> 2 z 23.6 20.4 -> y 23.6 19.2 FALSE #> 3 x 23.6 21.5 NA NA FALSE #> 4 y 23.6 19.2 NA NA FALSE
目前,ggdag支持有向(x~y)和双向(x~~y)关系
tidy_dagitty()使用R包ggraph和igraph中提供的布局函数创建坐标,可以使用layout参数指定坐标。使用ggdag()可以快速绘制tidy_dagitty或dagitty类的对象。如果DAG还没有整理成整洁的形式,ggdag()和ggdag中的大多数其他快速绘图函数都会在内部进行整理:
ggdag(dag, layout = 'circle')
tidy_dagitty对象是一个列表,包括一个叫做data的tbl_df数据框对象和一个叫做dag的dagitty对象:
tidy_dag <- tidy_dagitty(dagified)str(tidy_dag)#> List of 2#> $ data: tibble [4 × 8] (S3: tbl_df/tbl/data.frame)#> ..$ name : chr [1:4] 'z' 'z' 'x' 'y'#> ..$ x : num [1:4] 23.3 23.3 22.9 23.7#> ..$ y : num [1:4] 25.5 25.5 26.6 24.4#> ..$ direction: Factor w/ 3 levels '<-','->','<->': 2 2 NA NA#> ..$ to : chr [1:4] 'x' 'y' NA NA#> ..$ xend : num [1:4] 22.9 23.7 NA NA#> ..$ yend : num [1:4] 26.6 24.4 NA NA#> ..$ circular : logi [1:4] FALSE FALSE FALSE FALSE#> $ dag : 'dagitty' Named chr 'dag {\nx [exposure]\ny [outcome]\nz\nz -> x\nz -> y\n}\n'#> - attr(*, 'class')= chr 'tidy_dagitty'
(2)操作DAG
dagitty中的大部分分析函数在ggdag中都有对应的扩展,处理特定节点的命名为dag_*( ),处理整个DAG的命名为node_*( )。比如node_parents( ),它在tidy_dagitty对象中添加了一列关于给定变量的父对象:
node_parents(tidy_dag, 'x')#> # A DAG with 3 nodes and 2 edges#> ##> # Exposure: x#> # Outcome: y#> ##> # A tibble: 4 x 9#> name x y direction to xend yend circular parent#>#> 1 z 23.3 25.5 -> x 22.9 26.6 FALSE parent#> 2 z 23.3 25.5 -> y 23.7 24.4 FALSE parent#> 3 x 22.9 26.6NA NA FALSE child #> 4 y 23.7 24.4NA NA FALSE
或者和整个DAG一起,生成一个具有两个变量之间所有路径的tidy_dagitty对象:
bigger_dag <- dagify(y ~ x + a + b, x ~ a + b, exposure = 'x', outcome = 'y')# automatically searches the paths between the variables labelled exposure and # outcomedag_paths(bigger_dag) #> # A DAG with 4 nodes and 17 edges#> ##> # Exposure: x#> # Outcome: y#> ##> # A tibble: 20 x 10#> set name x y direction to xend yend circular path #> #> 1 1 a 17.3 19.2 -> x 16.8 18.2 FALSE #> 2 1 a 17.3 19.2 -> y 17.7 18.2 FALSE #> 3 1 b 17.3 17.2 -> x 16.8 18.2 FALSE #> 4 1 b 17.3 17.2 -> y 17.7 18.2 FALSE #> 5 1 x 16.8 18.2 -> y 17.7 18.2 FALSE open path#> 6 1 y 17.7 18.2 NA NA FALSE open path#> 7 2 a 17.3 19.2 -> x 16.8 18.2 FALSE open path#> 8 2 a 17.3 19.2 -> y 17.7 18.2 FALSE open path#> 9 2 b 17.3 17.2 -> x 16.8 18.2 FALSE #> 10 2 b 17.3 17.2 -> y 17.7 18.2 FALSE #> 11 2 x 16.8 18.2 -> y 17.7 18.2 FALSE #> 12 2 y 17.7 18.2 NA NA FALSE open path#> 13 2 x 16.8 18.2 -> y 17.7 18.2 FALSE open path#> 14 3 a 17.3 19.2 -> x 16.8 18.2 FALSE #> 15 3 a 17.3 19.2 -> y 17.7 18.2 FALSE #> 16 3 b 17.3 17.2 -> x 16.8 18.2 FALSE open path#> 17 3 b 17.3 17.2 -> y 17.7 18.2 FALSE open path#> 18 3 x 16.8 18.2 -> y 17.7 18.2 FALSE #> 19 3 y 17.7 18.2 NA NA FALSE open path#> 20 3 x 16.8 18.2 -> y 17.7 18.2 FALSE open path
ggdag支持dplyr,还支持函数的管道,并在内部包含管道:
library(dplyr)# find how many variables are in between x and y in each pathbigger_dag %>% dag_paths() %>%group_by(set) %>%filter(!is.na(path) & !is.na(name)) %>% summarize(n_vars_between = n() - 1L)#> # A tibble: 3 x 2#> set n_vars_between#>#> 1 1 1#> 2 2 3#> 3 3 3
(3)绘制DAG
大多数dag_*( )和node_*( )都有对应的ggdag_*()用于快速绘制图形。这些函数在内部调用dag_*( )或node_*( ),以ggplot2的形式绘制结果。如ggdag_paths( )绘制暴露和结果变量之间的所有路径,ggdag_parents()强调了父节点,ggdag_adjustment_set( )绘制协变量调整集:
###图4ggdag_paths(bigger_dag)###图5ggdag_parents(bigger_dag, 'x',node_size=8)+theme_void()###图6ggdag_adjustment_set(bigger_dag,node_size=8)+theme_void()
图-4
图-5
图-6
从这些图中可见,默认的ggdag_*()看起来就是ggplot2的形式,可以调整参数或者使用ggplot2的绘图函数对图形进行修改。
(4)使用ggplot2绘制DAG
大致上,ggdag( )和ggdag包里类似的函数dou都是ggplot2几何对象的打包,用于绘制节点、文本和边。例如,上文的ggdag_parents( )可以使用ggplot2和ggdag提供的几何对象实现:
###图7bigger_dag %>% node_parents('x') %>%ggplot(aes(x = x, y = y, xend = xend, yend = yend, color = parent)) +geom_dag_point(size=8) +geom_dag_edges() +geom_dag_text(col = 'white') +theme_dag() +scale_color_hue(breaks = c('parent', 'child'))
图-7
ggdag自己提供的一些几何对象:geom_dag_node()/geom_dag_point(),
geom_dag_edges(),geom_dag_text(),theme_dag()和scale_adjusted()。
geom_dag_node()略带风格(有一个内部的白圈),geom_dag_point()看起来更像是大号的geom_point()。theme_dag()移除了所有的轴和刻度,因为这些在因果模型中没有意义。expand_plot()绘图比例进行修改,使其更适合具有更大的点和文本的节点。scale_ adjusted()提供DAG分析中常见的默认值的调整,例如将调整变量的形状设置为正方形。
geom_dag_edges()用于绘制具有不同几何对象和箭头的定向边和双向边。有向边是带有一个箭头的直线,而双向线是两个双向变量(如a<-L->b)之间潜在父变量的简写,绘制为带有箭头的弧。也可以直接调用边函数,特别是只有有向边时。ggdag的很多边函数都来自ggraph,默认值(如箭头、截断线)是根据DAG进行设置的。目前,ggdag有四种类型的边几何对象:绘制直线的geom_dag_edges_link()、绘制圆弧的geom_dag_edges_arc()和geom_dag_edges_diagonal()、绘制扇形的geom_dag_edges_fan():
###图8dagify(y ~ x, m ~ x + y) %>% ggplot(aes(x = x, y = y, xend = xend, yend = yend)) + geom_dag_point() + geom_dag_edges_arc() + geom_dag_text() + theme_dag()
图8
如果有双向边,但希望按方向绘制,node_canonical()将自动插入潜变量:
###图9dagify(y ~ x + z,x ~~ z) %>% node_canonical() %>% ggplot(aes(x = x, y = y, xend = xend, yend = yend)) +geom_dag_point() +geom_dag_edges_diagonal() +geom_dag_text() +theme_dag()
图9