开发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
的开发经验后,我开始更加了解ggtree
与ggplot2
的底层设计思路。我觉得我可以为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
,treeio
与MicrobiotaProcess
包进行更新。主要是在tidytree
与MicrobiotaProcess
中引入tidy framework
,这样用户就可以使用dplyr
接口进行操作,同时MicrobiotaProcess
中提供的mp_
打头的功能也是基于tidy framework
的思想进行开发的。目前相关开发工作基本完成,我们希望能够提供一套统一的语法,方便用户处理微生物组数据,降低用户的学习成本。同时相关的方法学工作也可以基于此框架进行开发,就像该包中提供的差异分析功能mp_diff_analysis
也是基于此框架进行开发的。MicrobiotaProcess
还提供了衔接上游以及其他软件包的接口,如as.MPSE
可以将phyloseq
,SummarizedExperiment
,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
顺利见刊,但这不是终点。为了让更多的用户更好更快地处理相应的数据, 我们依然会继续开发维护相应的计算工具,并形成一个统一(语法统一)开放(开源)的计算生态,为科研社区的发展做点小贡献。