Chapter 10 ggtree utilities

10.1 facet utilities

10.1.1 facet_widths

library(ggplot2)
library(ggstance)
library(ggtree)
library(reshape2)

set.seed(123)
tree <- rtree(30)

p <- ggtree(tree, branch.length = "none") + 
    geom_tiplab() + theme(legend.position='none')

a <- runif(30, 0,1)
b <- 1 - a
df <- data.frame(tree$tip.label, a, b)
df <- melt(df, id = "tree.tip.label")

p2 <- facet_plot(p + xlim_tree(8), panel = 'bar', data = df, geom = geom_barh, 
                 mapping = aes(x = value, fill = as.factor(variable)), 
                 width = 0.8, stat='identity') + xlim_tree(9)

facet_widths(p2, widths = c(1, 2))

It also supports using name vector to set the widths of specific panels. The following code will display identical figure to Figure 10.1A.

facet_widths(p2, c(Tree = .5))

The facet_widths function also work with other ggplot object as demonstrated in Figure 10.1B.

p <- ggplot(iris, aes(Sepal.Width, Petal.Length)) + 
  geom_point() + facet_grid(.~Species)
facet_widths(p, c(setosa = .5))
Adjust relative widths of ggplot facets. The facet_widths function works with ggtree (A) as well as ggplot (B).

Figure 10.1: Adjust relative widths of ggplot facets. The facet_widths function works with ggtree (A) as well as ggplot (B).

10.1.2 facet_labeller

The facet_labeller function was designed to re-label selected panels, and it currently only works with ggtree object (i.e. facet_plot output).

facet_labeller(p2, c(Tree = "phylogeny", bar = "HELLO"))

If you want to combine facet_widths with facet_labeller, you need to call facet_labeller to re-label the panels before using facet_widths to set the relative widths of each panels. Otherwise it wont work since the output of facet_widths is re-drawn from grid object.

facet_labeller(p2, c(Tree = "phylogeny")) %>% facet_widths(c(Tree = .4))
Rename facet labels. Rename multiple labels simultaneously (A) or only for specific one (B) are all supported. facet_labeller can combine with facet_widths to rename facet label and then adjust relative widths (B).

Figure 10.2: Rename facet labels. Rename multiple labels simultaneously (A) or only for specific one (B) are all supported. facet_labeller can combine with facet_widths to rename facet label and then adjust relative widths (B).

10.2 Geometric layers

Subsetting is not supported in layers defined in ggplot2, while it is quite useful in phylogenetic annotation since it allows us to annotate at specific node(s) (e.g. only label bootstrap values that larger than 75).

In ggtree, we provides modified version of layers defined in ggplot2 to support aesthetic mapping of subset, including:

  • geom_segment2
  • geom_point2
  • geom_text2
  • geom_label2
library(ggplot2)
library(ggtree)
data(mpg)
p <- ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) +
   geom_point(mapping = aes(color = class)) + 
   geom_text2(aes(label=manufacturer, 
                  subset = hwy > 40 | displ > 6.5), 
                  nudge_y = 1) +
   coord_cartesian(clip = "off") +
   theme_light() +
   theme(legend.position = c(.85, .75))          

p2 <- ggtree(rtree(10)) + 
    geom_label2(aes(subset = node <5, label = label))
plot_grid(p, p2, ncol=2, labels=c("A", "B"))
Geometric layers that supports subsetting. Thes layers works with ggplot2 (A) and ggtree (B).

Figure 10.3: Geometric layers that supports subsetting. Thes layers works with ggplot2 (A) and ggtree (B).

10.3 Layout utilities

In session 4.2.2, we introduce several layouts that supported by ggtree. The ggtree package also provide several layout functions that can transfrom from one to another. Note that not all layouts are supported (see 10.1).

Table 10.1: Layout layers.
Layout Description
layout_circular transform rectangular layout to circular layout
layout_dendrogram transform rectangular layout to dendrogram layout
layout_fan transform rectangular/circular layout to fan layout
layout_rectangular transform circular/fan layout to rectangular layout
set.seed(2019)
x <- rtree(20)
p <- ggtree(x)
p + layout_dendrogram()
ggtree(x, layout = "circular") + layout_rectangular()
p + layout_circular()
p + layout_fan(angle=90)
Layout layers for transforming among different layouts. Default rectangular layout (A); transform rectangular to dendrogram layout (B); transform circular to rectangular layout (C); transform rectangular to circular layout (D); transform rectangular to fan layout (E).

Figure 10.4: Layout layers for transforming among different layouts. Default rectangular layout (A); transform rectangular to dendrogram layout (B); transform circular to rectangular layout (C); transform rectangular to circular layout (D); transform rectangular to fan layout (E).

10.4 Legend utilities

10.5 Scale utilities

10.5.1 xlim_expand

Sometimes we need to set xlim for specific panel (e.g. allocate more space for long tip labels at Tree panel). However, the ggplot2::xlim() function applies to all the panels. ggtree provides xlim_expand() to adjust xlim for user specific panel. It accepts two parameters, xlim and panel, and can adjust all individual panels as demonstrated in Figure 10.5A. If you only want to adjust xlim of the Tree panel, you can use xlim_tree() as a shortcut.

set.seed(2019-05-02)
x <- rtree(30)
p <- ggtree(x) + geom_tiplab()
d <- data.frame(label = x$tip.label, 
                value = rnorm(30))
p2 <- facet_plot(p, panel = "Dot", data = d, 
            geom = geom_point, mapping = aes(x = value))
p2 + xlim_tree(6) + xlim_expand(c(-10, 10), 'Dot')

The xlim_expand() function also works with ggplot2::facet_grid(). As demonstrating in Figure 10.5B, only the xlim of virginica panel was adjusted by xlim_expand().

g <- ggplot(iris, aes(Sepal.Length, Sepal.Width)) + 
    geom_point() + facet_grid(. ~ Species, scales = "free_x") 
g + xlim_expand(c(0, 15), 'virginica')
Setting xlim for user specific panel. xlim for ggtree::facet_plot (A, Tree and Dot panels), and ggplot2::facet_grid (B, virginica panel).

Figure 10.5: Setting xlim for user specific panel. xlim for ggtree::facet_plot (A, Tree and Dot panels), and ggplot2::facet_grid (B, virginica panel).

10.5.2 Reconcile axis limits

Suppose we have the following plots and would like to combine them in a single page.

library(dplyr)
library(ggplot2)
library(ggstance)
library(ggtree)
library(cowplot)

no_legend=theme(legend.position='none')

d <- group_by(mtcars, cyl) %>% summarize(mean=mean(disp), sd=sd(disp)) 
d2 <- dplyr::filter(mtcars, cyl != 8) %>% rename(var = cyl)

p1 <- ggplot(d, aes(x=cyl, y=mean)) + 
    geom_col(aes(fill=factor(cyl)), width=1) + 
    no_legend
p2 <- ggplot(d2, aes(var, disp)) + 
    geom_jitter(aes(color=factor(var)), width=.5) + 
    no_legend

p3 <- ggplot(filter(d, cyl != 4), aes(mean, cyl)) + 
    geom_colh(aes(fill=factor(cyl)), width=.6) + 
    coord_flip() + no_legend

pp <- list(p1, p2, p3)

We can use cowplot or patchwork to combine plots.

plot_grid(plotlist=pp, ncol=1, align='v')

However, these plots do not align properly (Figure 10.6A).

There are two reasons:

  • the plotted data have different limits
  • the different plots have different amounts of expansion spaces

To address these two issues, ggtree provides xlim2() and ylim2() functions to set x or y limits20. It use input limits to set axis limits that is similar to xlim() and ylim() (Figure 10.6B). If limits = NULL (by default), the xlim2() and ylim2() functions will calculate axis limits from input ggplot object. So that we can easily set limits of a ggplot object based on another ggplot object to uniformize their limits (Figure 10.6C).

pp2 <- lapply(pp, function(p) p + xlim2(limits=c(3, 11)))
pp3 <- lapply(pp, function(p) p + xlim2(p1))

plot_grid(plotlist=pp2, ncol=1, align='v')
plot_grid(plotlist=pp3, ncol=1, align='v')

If the plot was flipped, it will throw a message and apply the another axis. In this example, the x limit of p1 is applied to y limit of p3 as p3 was flipped.

Setting axis limits for aligning plots. Composite plot that does not align properly (A column), align based on user specific limits (B column), and align based on xlim of p1 object (C column).

Figure 10.6: Setting axis limits for aligning plots. Composite plot that does not align properly (A column), align based on user specific limits (B column), and align based on xlim of p1 object (C column).

An example of using ylim2() to reconcile y axis can be found in session 7.5.

10.5.2.1 Creating annotated heatmap

The xlim2() and ylim2() functions create many possibilities to align figures. For instance, we can add column and row annotations around a heatmap in all sides (top, bottom, left and right). They can be aligned properly with the aids of xlim2() and ylim2() even with missing values presented as demonstrated in Figure 10.7.

library(cowplot)
library(tidyr)
library(ggplot2)
library(ggtree)

set.seed(2019-11-07)
d <- matrix(rnorm(25), ncol=5)
rownames(d) <- paste0('g', 1:5)
colnames(d) <- paste0('t', 1:5)
hc <- hclust(dist(d))
hcc <- hclust(dist(t(d)))
phr <- ggtree(hc)
phc <- ggtree(hcc) + layout_dendrogram()

d <- data.frame(d)
d$gene <- rownames(d)
lv <- d$gene[hc$order]
lvc <- colnames(d)[hcc$order]
dd <- gather(d, 1:5, key="condition", value='expr')
dd$gene <- factor(dd$gene, levels=lv)
dd$condition <- factor(dd$condition, levels=lvc)

bs <- 14 
p <- ggplot(dd, aes(condition,gene, fill=expr)) + geom_tile() + 
  scale_fill_viridis_c() +
  scale_x_discrete(position="top") +
  theme_minimal(base_size=bs) + 
  theme(legend.position='none') + 
  xlab(NULL) + ylab(NULL) 

g <- ggplot(dplyr::filter(dd, gene != 'g2'), aes(gene, expr, fill=gene)) + 
  geom_boxplot() + coord_flip() +
  scale_fill_brewer(palette = 'Set1') +
  theme_minimal(base_size=bs) + 
  theme(legend.position='none',
        axis.text.y = element_blank(), 
        axis.ticks.y = element_blank(),
        panel.grid.minor = element_blank(),
        panel.grid.major.y = element_blank()) +
  xlab(NULL) + ylab(NULL) 

ca <- data.frame(n = paste0('t', 1:5), 
                 anno1 = rep(LETTERS[1:2], times=c(3, 2)),
                 anno2 = rep(letters[3:5], times=c(1, 3, 1))
)
cad <- gather(ca, anno1, anno2, key='anno', value='type')
cad$n <- factor(cad$n, levels = lvc)

pc <- ggplot(cad, aes(n, y=anno, fill=type)) + geom_tile() + 
  theme_minimal(base_size = bs) + 
  theme(legend.position='none',
        axis.text.x = element_blank(), 
        axis.ticks.x = element_blank()) +
  xlab(NULL) + ylab(NULL) 

set.seed(123)
dp <- data.frame(gene=factor(rep(paste0('g', 1:5), 2), levels=lv), 
                 pathway = sample(paste0('pathway', 1:5), 10, replace = TRUE))

pp <- ggplot(dp, aes(pathway, gene)) + 
  geom_point(size=5, color='steelblue') +
  scale_x_discrete(position = "top") +
  theme_minimal(base_size=bs) +
  theme(axis.text.x=element_text(angle=90, hjust=0),
        axis.text.y = element_blank(), 
        axis.ticks.y = element_blank()) +
  xlab(NULL) + ylab(NULL) 

legend <- plot_grid(get_legend(pc + theme(legend.position="bottom")),
                    get_legend(p + theme(legend.position="bottom")), ncol=1)

## plot_grid(NULL, phc + xlim2(p), legend, NULL,
##           NULL, pc+xlim2(p), NULL, NULL,
##           phr+ylim2(p), p, g + ylim2(p), pp + ylim2(p), ncol=4, 
##           rel_widths=c(.3, 1, .4, .4), rel_heights=c(.3,.2, 1), align='hv')

library(patchwork)
plot_spacer() + (phc+xlim2(p)) + legend + plot_spacer() +
  (phr+ylim2(p)) + p + (g+ylim2(p)) + (pp+ylim2(p)) + 
  plot_spacer() + (pc+xlim2(p)) + plot_spacer() + plot_spacer() +
  plot_layout(ncol=4, widths = c(.2, 1, .3, .3), heights=c(.2, 1, .1))
Create complex heatmap. With the helps of xlim2() and ylim2(), it is easy to align row or column annotations around a figure (e.g. a heatmap).

Figure 10.7: Create complex heatmap. With the helps of xlim2() and ylim2(), it is easy to align row or column annotations around a figure (e.g. a heatmap).