如何使用R语言制作有向无环图(DAG)

有向无环图(DAG)是什么?

有向无环图(DAG)全称为Directed Acyclic Graph。理解这个概念,我们可以拆分为三部分:

  • 首先它要是图。这里的图是指“图论”所讲的图,最简单来说就是要有点和连接点的边。
  • 然后,它要有方向,也就是从一个点到另一个点的那条边需要有箭头指向,就好像我们说的,从A点到B点。
  • 最后,它必须无环。假如我们从A点出发,通过各种边到达其他不同的点,但最后绝对不可以再回到A点。

有向无环图和“我”有什么关系?

有向无环图(DAG)对于数学以及计算机科学以外的领域其实也是有意义的。从流行病学、生物统计来说,有向无环图主要用来进行因果推断。研究者们会通过已知的理论框架来构建自己的DAG、确定研究假设、确定统计分析的思路。DAG更多时候是作为一个更加直观的方式来梳理和呈现一个研究者的研究框架。这种梳理和呈现是非常有意义的,因为它会指导你如何设计你的研究以及如何分析你的数据。

例如,你想研究“奶茶是否会造成青少年肥胖症”。你的结果是“肥胖症”,你的暴露是“奶茶”。但你也知道还有各种因素的存在,这些因素可能会和你的结果或是暴露产生各种各样的关系。假如,你看到一些研究在说“喝了奶茶,青少年会更瞌睡,更不想运动”,所以你认为“运动减少”是处于“奶茶”和“肥胖症”的因果关系之间的。再假如,你发现,很多青少年在吃炸鸡的时候才会喝奶茶,所以你觉得很可能是“吃炸鸡”造成了“喝奶茶”,而你同时也认为炸鸡本身也会导致肥胖症。那么,这里的“炸鸡”就有可能是真正的罪魁祸首,而很可能“奶茶”并不是肥胖症的真正原因。

当你在设计研究的时候,你所要考虑的可能远不止“奶茶”、“炸鸡”和“肥胖症”,你可能要考虑性别、地域、受教育程度、经济条件等等。面对这么多因素,到底彼此之间有无关系、有什么关系、会不会影响你关注的那条主要的因果关系,你可能需要用更加严谨和清楚的方式来梳理和呈现这些关系,那这个时候你可能就需要DAG了。

关于DAG的几个关键概念

  • 父代(Parent)和子代(Child):直接的因果关系。例如,A直接指向B,A就是父代,B就是子代。
  • 祖代(Ancestors)和后代(descendants):直接和间接的因果关系。例如,A指向B指向C指向D,那么,ABC都是D的祖代,BCD都是A的后代。
  • 暴露(Exposure):你关心的自变量X
  • 结果(Outcome):你关心的因变量Y
  • 共变量(Covariates):其他所有你研究中的变量。它们会有不同的身份,如下。
  • 混杂变量(Confounders):既导致暴露又导致结果。例如,吃炸鸡导致了喝奶茶,吃炸鸡本身也会导致肥胖症。
  • 媒介变量(Mediators):处于暴露和结果这条因果链条中间的变量。例如,喝奶茶导致运动减少进而导致肥胖症。
  • 代理混杂变量(Proxy Confounders):你可以理解为混杂变量和结果之间的媒介变量,也就是代理混杂变量不是真正的混杂,但它是真正混杂的代理商。
  • 竞争变量(Competing Exposures):所有那些其他的变量。这其中会包括效应修饰变量(它的存在或不存在以及不同赋值会让你关心的因果关系的强度发生明显变化)或交互变量(因为它和暴露一起出现,导致结果明显增多)。不过,很多时候效应修饰和交互会被当作一回事。
  • 其他概念诸如d-Separation(用来分析是否是独立、是否存在冲突变量等)在这里就不多赘述了。

如何使用R语言制作DAG

  • 假设:在中国男性群体中,吸食冰毒的使用会导致HIV感染。
  • 暴露:最近12个月内是否吸食过冰毒
  • 结果:是否感染HIV
  • 共变量:性取向、年龄、受教育程度、职业、月收入、最近12个月注射型毒品的使用、最近12个月是否发生无保护性性行为、最近12个月是否有过度饮酒
  • 媒介变量:无保护性性行为(假设,吸食冰毒会增强性欲,并且降低理性判断能力,进而更有可能发生无保护性性行为,仅仅导致HIV感染)
  • 混杂变量:最近12个月注射型毒品的使用(假设,使用注射型毒品的人更可能吸食冰毒,他们本身也更容易发生无保护性行为,而注射型毒品本身就可能导致HIV感染)
  • 效应修饰变量:性取向(不同性取向在冰毒吸食上可能有差异,不同性取向的人因为不同的性行为方式进而有不同的感染可能性)、年龄(年龄差异)、受教育程度、职业(娱乐场所从业人员可能更有机会接触冰毒)、月收入(高收入人群可能有更大比例的人吸食冰毒)
  • 交互变量:最近12个月是否过度饮酒(假设,这个和吸食冰毒同时出现,会大大增加感染HIV的可能性,具体原因可能有很多)

PS:一个小问题,R语言用来制作DAG的包目前还不能够给点贴上中文标签,在这里就先使用英文标签。

# 下载两个包:dagitty和ggdag
install.packages("dagitty")   
install.packages("ggdag")

# 加载这两个包
library(dagitty)
library(ggdag)

# 使用dagitty包当中的dagitty函数来创建DAG
# X,Y,Z这些字母都是随便的,你可以想用什么字母用什么,大写小写都可以
# 这些字母代表你的变量
# ->代表方向
# 每一个;表示一个具体的点边关系
# {}代表组,例如{A R D J S}都是效应修饰变量,组里的每个字母用空格隔开
dag <- dagitty:: dagitty("dag{
                         X -> Z -> Y; X <- U -> Y;
                         {A R D J S} -> Y;
                         {B I} -> Z; 
                         I -> X; I -> Y;
                         D -> S; D -> J;
                         R -> Z;
                  }") 
# 使用ggdag包
# 使用tidy_dagitty让原来的dag变成tidy形式,方便使用其他函数来修饰
# 使用dag_label给dag当中的每一个变量搭配唯一一个标签,
# 一定注意中英文输入法状态下的标点符号是不同的,代码需要用英文输入法的符号
# 尤其区分“”和"",两个不同,第一个不对
# seed的值随便,设置seed是为了可重复性,不设置的话,每次的图都有差别
dag_tidy <- ggdag::tidy_dagitty(dag, seed = 4) %>%
  dag_label(labels = c("X" = "Meth Use",  
                       "Y" = "HIV", "U" = "Unobserved Confounder",  
                       "A" = "Age", "B" = "Binge Drink",  
                       "D" = "Education", "J" = "Occupation", 
                       "S" = "Monthly Income", "R" = "Sexual Orientation",  
                       "I" = "Injection Drug", "Z" = "Condomless Sex"))
# 使用ggdag函数来制作DAG
# dag_tidy是你的dag数据,node_size可以修改点的大小,text_size可以修改字母大小
# label_size修改标签大小,edge_type是边的类型,可以是直的,也可以是弯的
# geom_dag_label_repel里面是添加标签的函数,最好不要变动
# geom_dag_edges可以改变边的属性,我这里改变了宽度
# theme_dag_blank可以删掉背景
ggdag(dag_tidy, node_size = 15, text_size = 5, label_size = 5,
      edge_type = "link_arc") +
  geom_dag_label_repel(aes(label = label)) +
  geom_dag_edges(edge_width = 1) +
  theme_dag_blank() +
  expand_plot(expand_x = expansion(c(0.1, 0.1)),
              expand_y = expansion(c(0.1, 0.1))) 

效果如下图:

Rplot01

写在后面

事实上,在社会科学领域,因果关系一直都被非常谨慎地使用。社会问题非常复杂,更多时候可能仅限于相关关系上。我们都知道因果推断,时序性是非常重要的,原因一定要早于结果发生,而社会科学本身的研究设计尤其自身的局限性,进而很难满足时序性要求。但是,这并不代表社会科学不需要追求因果推断,不需要DAG。其实,DAG本身并不是因果关系本身,它更多的是一个直观展示你的变量直接关系的图。当你在确定一个研究问题,有自己的假设的时候,其实基于文献,你是有大概的理论框架的,或者你本身有个设想,设想是可以以因果关系的形式出现。只不过是因为你研究设计本身以及其他原因导致你的结果在因果推断的效力上比较弱罢了。