ggtreeExtra的开发及其在宏基因组上的应用

开发ggtreeExtra的初衷

因为我之前工作的时候主要是做微生物组学数据的分析工作,工作中往往需要对这些数据进行可视化以方便数据展示与解析,简单的可视化还好,有ggplot2就行了。然而,微生物组学数据分析中往往需要将相关的外部数据信息与进化树或者是物种层级树联系起来才能更好展示并解析相关结果,而对于这样的操作来说,目前的很多工具基本都难以使用,除了ggtree。因为该软件包继承了ggplot2的语法,所以只要用户会ggplot2的语法,也自然会使用ggtree。然而ggtree提供了geom_facet图层(用来联接进化树与外部数据)不支持环形布局,一旦树节点变多,所生成的图片就可能显得很拥挤。所以,我问我老板,要不要开发一个可以兼容环形布局的进化树的功能,以此来节省空间方便多维数据的展示解析,这样ggtree的应用也会更广。他欣然同意,并指点我可以参考ggtree中的gheatmap功能, 因为该功能也支持环形布局,并且所开发功能得与geom_facet的功能相似,这样才能有一个统一的语法从而方便用户使用。

ggtreeExtra开发时的主要问题解决

确定好这个小项目可以做之后,碰到的第一个问题就是如何才能将环形树与外部图层连接起来,而且必需使用ggplot2的语法。因为在这之前,我为了更为深入了解ggplot2的架构,在去年疫情时按着老板的gglayer开发了ggstar。有了这个基础后,我开始了解到ggplot2图层的一些抽象思维。不同的图层叠加组合往往可以得到更丰富的结果。我开始注意到ggtree绘制进化树图层时,是事先对树结构进行解析将节点转换成坐标进而用线条连接。如果打完进化树图层后,再直接用ggplot2的图层叠加会发现,这个图层与树图层往往会重叠。所以在某一天回家的路上,我突然想到如果这个叠加的图层可以平移不就可以了?于是,第一个问题开始变为如何平移叠加的图层?我开始返回去看gheatmap的功能,突然发现该功能也是类似的思想,就是给热图图层加一个数值,让其从原来的位置跑到进化树外部,但是操作起来很麻烦。我又返回去看ggplot2的源代码,发现图层位置其实就是通过Position对象来确定的。于是乎开始实现各种图层的Position对象(因为不同的图层可能会有不同的Position对象)。开发后初步测试(将原来的图层中的position换成我开发的position功能), 发现确实可以工作。然而,当树的数据与外部数据不是同一个数量级时,就可能导致要么进化树被压缩要么是外部图层。于是,第二个难题出现,对于这种不同数据级的进化树与外部数据要怎么操作才可能更好的展示?又是在回家的路上,我想到之前数据建模中,在数据清洗阶段,往往也会碰到这种不同的数量级的数据,而对于这种数据,我们可以采用将其标准化为某个区间,比如常用的0到1之间。这种标准化操作不会改变数据分布的,所以第二个问题的解决方案就变成了将外部数据统一标准化为进化树的数据范围同时保持原来的数据。因为有之前的基础,所以这个问题也就很快搞定。后续的开发过程中也碰到一些难题,不过凭借着几年经验的积累最终还是被一一解决。而前面所列的主要两个问题是这个工作的突破口,所以单独拿出来讲下。

后续工作

有了ggtreeExtra的开发经验后,我开始更加了解ggtreeggplot2的底层设计思路。我觉得我可以为ggtree的开发做点贡献了。于是,我尝试为ggtree的一些图层功能(比如geom_hilight,geom_cladelab,geom_taxalink,geom_tiplab…)进行更新,主要是让其更好的兼容图形语法 (mapping aesthetics),从而更加方便地展示相关数据。ggtree,ggtreeExtra都是通用的工具,与ggplot2有相似的语法,对于输入数据本质上都是tidy data(每一行代表一个观测值,每一列代表一个变量), 这种数据结构近几年也在R语言社区中广为流行,以Rstudio为代表的开发团队,开发了dplyr,tidyr, tibble, ggplot2, forcats, purrr等系列软件包基本都是基于该数据结构。结合我之前的微生物组学数据分析工作,最近,我们也开始对tidytree,treeioMicrobiotaProcess包进行更新。主要是在tidytreeMicrobiotaProcess中引入tidy framework,这样用户就可以使用dplyr接口进行操作,同时MicrobiotaProcess中提供的mp_打头的功能也是基于tidy framework的思想进行开发的。目前相关开发工作基本完成,我们希望能够提供一套统一的语法,方便用户处理微生物组数据,降低用户的学习成本。同时相关的方法学工作也可以基于此框架进行开发,就像该包中提供的差异分析功能mp_diff_analysis也是基于此框架进行开发的。MicrobiotaProcess还提供了衔接上游以及其他软件包的接口,如as.MPSE可以将phyloseqSummarizedExperiment,TreeSummarizedExperiment转为MPSE对象,mp_import_metaphlan用于解析MetaPhlan3的输出结果等。

library(ggstar) library(ggplot2) library(forcats) library(ggtree) library(tidytree) library(ggtreeExtra) library(MicrobiotaProcess) 

`data(mouse.time.mpse)

MPSE 对象,继承了 SummarizedExperiment 与 treedata 对象

以方便后续数据的处理操作,以及功能拓展。

在print的时候结合tibble的样式对MPSE对象整理成tidy data展示

每一行代表一个OTU(特征)在每个样本中的丰度信息,以及相应的

样本信息与物种分类信息。

mouse.time.mpse %<>%

稀释抽平功能,有研究评估抽平对于微生物组数据有较好的鲁棒性

mp_rrarefy(.abundance=Abundance) %>% 

默认计算每个样品中所有层级的物种相对丰度(relative=TRUE)

mp_cal_abundance(.abundance=RareAbundance) %>%

计算time分组下的每个组别的所有层级的物种相对丰度

mp_cal_abundance(.abundance=RareAbundance, .group=time)` 
## The otutree is empty in the MPSE object!## The otutree is empty in the MPSE object!

# 所得的结果存于SummarizedExperiment中的assays内 mouse.time.mpse 

## # A MPSE-tibble (MPSE object) abstraction: 4,408 x 13## # OTU=232 | Samples=19 | Assays=Abundance, RareAbundance, RelRareAbundanceBySample | Taxanomy=Kingdom, Phylum, Class, Order, Family, Genus, Species##    OTU   Sample Abundance RareAbundance RelRareAbundanceB… time  Kingdom Phylum ##                                         ##  1 OTU_1 F3D0         579           214               8.50 Early k__Bac… p__Bac…##  2 OTU_1 F3D1         405           202               8.02 Early k__Bac… p__Bac…##  3 OTU_2 F3D0         345           116               4.61 Early k__Bac… p__Bac…##  4 OTU_2 F3D1         353           183               7.27 Early k__Bac… p__Bac…##  5 OTU_3 F3D0         449           179               7.11 Early k__Bac… p__Bac…##  6 OTU_3 F3D1         231           122               4.85 Early k__Bac… p__Bac…##  7 OTU_4 F3D0         430           167               6.63 Early k__Bac… p__Bac…##  8 OTU_4 F3D1          69            36               1.43 Early k__Bac… p__Bac…##  9 OTU_5 F3D0         154            54               2.14 Early k__Bac… p__Bac…## 10 OTU_5 F3D1         140            69               2.74 Early k__Bac… p__Bac…## # … with 4,398 more rows, and 5 more variables: Class , Order ,## #   Family , Genus , Species 

`trda <- mouse.time.mpse %>%
mp_diff_analysis(.abundance=RelRareAbundanceBySample,
.group=time,
first.test.alpha=0.05)

默认得到treedata对象(含有差异统计值以及每个样本的物种相对丰度信息)

为了更好地展示associated data的数据,tidytree最新版也将treedata对象整理成

tidy data格式进行展示。

associated data中的每一行代表树结构上的一个节点,前三列信息来着tree结构

分别为节点Index,节点名称以及是否为树的端点,其他信息均为外部相关信息。

trda`

## 'treedata' S4 object'.## ## ...@ phylo:## ## Phylogenetic tree with 232 tips and 218 internal nodes.## ## Tip labels:##   OTU_58, OTU_67, OTU_231, OTU_188, OTU_150, OTU_207, ...## Node labels:##   r__root, k__Bacteria, p__Actinobacteria, p__Bacteroidetes, p__Cyanobacteria, p__Deinococcus-Thermus, ...## ## Rooted; no branch lengths.## ## with the following features available:##  'nodeClass',    'nodeDepth',    'RareAbundanceBySample',    'RareAbundanceBytime',##  'LDAupper', 'LDAmean',  'LDAlower', 'Sign_time',    'pvalue',   'fdr'.## ## # The associated data tibble abstraction: 450 x 13## # The 'node', 'label' and 'isTip' are from the phylo tree.##     node label   isTip nodeClass nodeDepth RareAbundanceBySam… RareAbundanceByt…##                                            ##  1     1 OTU_58  TRUE  OTU               8     ##  2     2 OTU_67  TRUE  OTU               8     ##  3     3 OTU_231 TRUE  OTU               8     ##  4     4 OTU_188 TRUE  OTU               8     ##  5     5 OTU_150 TRUE  OTU               8     ##  6     6 OTU_207 TRUE  OTU               8     ##  7     7 OTU_5   TRUE  OTU               8     ##  8     8 OTU_80  TRUE  OTU               8     ##  9     9 OTU_163 TRUE  OTU               8     ## 10    10 OTU_1   TRUE  OTU               8     ## # … with 440 more rows, and 6 more variables: LDAupper , LDAmean ,## #   LDAlower , Sign_time , pvalue , fdr 

`# 展开观察样本相对丰度信息列所含有的信息

tidy data 数据结构中每一行的观察值,可以是一系列样本嵌套起来的结构

如这里的RareAbudanceBySample就是每一行代表一个节点(物种)在所有样本下的相对丰度以及计算

这个相对丰度所用的丰度信息,还含有每个样本的分组信息。

一般就用tidyr nest就可以对这些信息进行折叠

该unnest是开发版tidytree (>=0.3.4.992)提供的

trda %>% unnest(RareAbundanceBySample)`

## # A tbl_df is returned for independent data analysis.## # A tibble: 8,531 x 16##     node label  isTip nodeClass nodeDepth Sample RareAbundance RelRareAbundance…##                                         ##  1     1 OTU_58 TRUE  OTU               8 F3D0               0                 0##  2     1 OTU_58 TRUE  OTU               8 F3D1               0                 0##  3     1 OTU_58 TRUE  OTU               8 F3D141             0                 0##  4     1 OTU_58 TRUE  OTU               8 F3D142             0                 0##  5     1 OTU_58 TRUE  OTU               8 F3D143             0                 0##  6     1 OTU_58 TRUE  OTU               8 F3D144             0                 0##  7     1 OTU_58 TRUE  OTU               8 F3D145             0                 0##  8     1 OTU_58 TRUE  OTU               8 F3D146             0                 0##  9     1 OTU_58 TRUE  OTU               8 F3D147             0                 0## 10     1 OTU_58 TRUE  OTU               8 F3D148             0                 0## # … with 8,521 more rows, and 8 more variables: time ,## #   RareAbundanceBytime , LDAupper , LDAmean , LDAlower ,## #   Sign_time , pvalue , fdr 

# RareAbundanceBytime则是每个分组下的物种丰度信息 trda %>% unnest(RareAbundanceBytime) 

## # A tbl_df is returned for independent data analysis.## # A tibble: 898 x 15##     node label isTip nodeClass nodeDepth RareAbundanceBy… time  RareAbundanceBy…##                                        ##  1     1 OTU_… TRUE  OTU               8 ,## #   LDAupper , LDAmean , LDAlower , Sign_time ,## #   pvalue , fdr 

利用ggtree与ggtreeExtra进行可视化

`# p1 绘制内部的环形进化树图

td_filter 可以对plot data进行操作 (td_filter(nodeClass == "Phylum")(p1$data))

p1 <- ggtree(
trda,
layout="radial",
size = 0.3
) +
geom_point(
data = td_filter(!isTip),
fill="white",
size=1,
shape=21
)

p2 绘制中间门水平的高亮图层

p2 <- p1 +
geom_hilight(
data = td_filter(nodeClass == "Phylum"),
mapping = aes(node = node, fill = label)
)

p3 绘制外圈的点图,用来展示不同OTU在每个样本中的丰度信息

td_unnest 与 td_filter类似

p3 <- p2 +
ggnewscale::new_scale("fill") +
geom_fruit(
data = td_unnest(RareAbundanceBySample),
geom = geom_star,
mapping = aes(
x = fct_reorder(Sample, time, .fun=min),
size = RelRareAbundanceBySample,
fill = time,
subset = RelRareAbundanceBySample > 0
),
starshape = 13,
starstroke = 0.25,
offset = 0.04,
pwidth = 0.8,
grid.params = list(linetype=2)
) +
scale_size_continuous(
name="Relative Abundance (%)",
range = c(1, 3)
) +
scale_fill_manual(values=c("#1B9E77", "#D95F02"))

p4 绘制tip节点的名称,为了更好地观察,将其标注在点图外

p4 <- p3 + geom_tiplab(size=2, offset=7.2)

p5 绘制组间有差异的OTU的线性判别效应值(log10(LDA))

p5 <- p4 +
ggnewscale::new_scale("fill") +
geom_fruit(
geom = geom_col,
mapping = aes(
x = LDAmean,
fill = Sign_time,
subset = !is.na(LDAmean)
),
orientation = "y",
offset = 0.3,
pwidth = 0.5,
axis.params = list(axis = "x",
title = "Log10(LDA)",
title.height = 0.01,
title.size = 2,
text.size = 1.8,
vjust = 1),
grid.params = list(linetype = 2)
)

p6 绘制所有分类水平中组间经过kruskal.test(默认)检验并进行FDR校正的FDR值

p6 <- p5 +
ggnewscale::new_scale("size") +
geom_point(
data=td_filter(!is.na(fdr)),
mapping = aes(size = -log10(fdr),
fill = Sign_time,
),
shape = 21,
) +
scale_size_continuous(range=c(1, 3)) +
scale_fill_manual(values=c("#1B9E77", "#D95F02"))

p6 + theme(
legend.key.height = unit(0.3, "cm"),
legend.key.width = unit(0.3, "cm"),
legend.spacing.y = unit(0.02, "cm"),
legend.text = element_text(size = 7),
legend.title = element_text(size = 9),
)`

图片

总结

ggtreeExtra顺利见刊,但这不是终点。为了让更多的用户更好更快地处理相应的数据, 我们依然会继续开发维护相应的计算工具,并形成一个统一(语法统一)开放(开源)的计算生态,为科研社区的发展做点小贡献。

你可能感兴趣的:(ggtreeExtra的开发及其在宏基因组上的应用)