##' drawing phylogenetic trees from list of phylo objects
##'
##' The trees plotted by 'ggdensitree()' will be stacked on top of each other and the
##' structures of the trees will be rotated to ensure the consistency of the tip order.
##'
##' @title ggdensitree
##' @param data a list of phylo objects or any object with an as.phylo and fortify method
##' @param mapping aesthetic mapping
##' @param layout one of 'slanted', 'rectangluar', 'fan', 'circular' or 'radial' (default: 'slanted')
##' @param tip.order the order of the tips by a character vector of taxa names; or an integer, N, to order the tips by the order of the tips in the Nth tree; 'mode' to order the tips by the most common order; 'mds' to order the tips based on MDS of the path length between the tips; or 'mds_dist' to order the tips based on MDS of the distance between the tips (default: 'mode')
##' @param align.tips TRUE (default) to align trees by their tips and FALSE to align trees by their root
##' @param jitter deviation to jitter tips
##' @param ... additional parameters passed to fortify, ggtree and geom_tree
##' @return tree layer
##' @importFrom magrittr %<>%
##' @importFrom magrittr add
##' @export
##' @author Yu Guangchuang, Bradley R. Jones
##' @references
##' For more detailed demonstration of this function, please refer to chapter 4.4.2 of
##' *Data Integration, Manipulation and Visualization of Phylogenetic Trees*
##' <http://yulab-smu.top/treedata-book/index.html> by Guangchuang Yu.
##' @examples
##' require(ape)
##' require(dplyr)
##' require(tidyr)
##'
##' # Plot multiple trees with aligned tips
##' trees <- list(read.tree(text="((a:1,b:1):1.5,c:2.5);"), read.tree(text="((a:1,c:1):1,b:2);"));
##' ggdensitree(trees) + geom_tiplab()
##'
##' # Plot multiple trees with aligned tips with tip labels and separate tree colors
##' trees.fort <- list(trees[[1]] %>% fortify %>%
##' mutate(tree="a"), trees[[2]] %>% fortify %>% mutate(tree="b"));
##' ggdensitree(trees.fort, aes(colour=tree)) + geom_tiplab(colour='black')
##'
##'
##' # Generate example data
##' set.seed(1)
##' random.trees <- rmtree(5, 10)
##' time.trees <- lapply(seq_along(random.trees), function(i) {
##' tree <- random.trees[[i]]
##' tree$tip.label <- paste0("t", 1:10)
##' dates <- estimate.dates(tree, 1:10, mu=1, nsteps=1)
##' tree$edge.length <- dates[tree$edge[, 2]] - dates[tree$edge[, 1]]
##' fortify(tree) %>% mutate(tree=factor(i, levels=as.character(1:10)))
##' })
##'
##' # Plot multiple trees with aligned tips from multiple time points
##' ggdensitree(time.trees, aes(colour=tree), tip.order=paste0("t", 1:10)) + geom_tiplab(colour='black')
##'
##'
##' # Read example data
##' example.trees <- read.tree(system.file("examples", "ggdensitree_example.tree", package="ggtree"))
##'
##' # Compute OTU
##' grp <- list(A = c("a.t1", "a.t2", "a.t3", "a.t4"),
##' B = c("b.t1", "b.t2", "b.t3", "b.t4"),
##' C = c("c.t1", "c.t2", "c.t3", "c.t4"))
##' otu.trees <- lapply(example.trees, groupOTU, grp)
##'
##' # Plot multiple trees colored by OTU
##' ggdensitree(otu.trees, aes(colour=group), alpha=1/6, tip.order='mds') +
##' scale_colour_manual(values=c("black", "red", "green", "blue"))
ggdensitree <- function(data=NULL, mapping=NULL, layout="slanted", tip.order='mode',
align.tips=TRUE, jitter=0, ...) {
## factorize to simplify code
trees <- lapply(data, as.phylo)
trees.f <- lapply(data, fortify, layout=layout, ...)
n.tips <- sum(trees.f[[1]]$isTip)
## determine tip order
if (length(tip.order) == 1) {
if (tip.order == 'mode') {
first.label <- trees.f[[1]] %>%
dplyr::filter(.data$isTip) %>%
dplyr::arrange(.data$y) %>%
dplyr::pull(.data$label)
res <- lapply(
trees.f,
. %>%
dplyr::filter(.data$isTip) %>%
dplyr::arrange(.data$y) %>%
dplyr::pull("label") %>%
match(first.label)
) %>%
do.call(rbind, .) %>%
as.data.frame() %>%
dplyr::group_by_all() %>%
dplyr::summarize(count = dplyr::n(), .groups = 'drop') %>%
dplyr::filter(.data$count == max(.data$count)) %>%
dplyr::select(-.data$count) %>%
dplyr::slice(1) %>%
unlist()
tip.order <- first.label[res]
} else if (grepl('mds', tip.order)) {
method <- tip.order
first.label <- trees.f[[1]] %>%
dplyr::filter(.data$isTip) %>%
dplyr::pull(.data$label)
tip.order <- lapply(trees, function(x) {
match(x$tip.label, first.label)
})
tip.2.tip <- lapply(trees, .cophenetic.phylo.check.length, method=method)
tip.2.tip <- lapply(1:length(trees), function(i) {
tip.2.tip[[i]][tip.order[[i]], tip.order[[i]]]
})
all.tab <- do.call(rbind, tip.2.tip)
rownames(all.tab) <- NULL
distances <- stats::dist(t(all.tab))
res <- stats::cmdscale(distances, k=1)
tip.order <- first.label[order(res[,1])]
} else if (as.numeric(tip.order) && tip.order <= length(trees)) {
labels <- trees.f[[tip.order]] %>%
dplyr::filter(.data$isTip) %>%
dplyr::pull(.data$label)
tip.order <- labels[as.integer(trees.f[[tip.order]]$y)]
}
}
## reorder tips (and shift x id align tips)
max.x <- vapply(trees.f, function(x) max(x$x, na.rm = TRUE), numeric(1))
trees.f <- lapply(1:length(trees), function(i) {
trees.f[[i]]$y <- getYcoord(trees[[i]], tip.order = tip.order)
if (align.tips) {
trees.f[[i]]$x <- trees.f[[i]]$x - max.x[i]
}
if (i > 1 && jitter > 0) {
trees.f[[i]]$y[1:n.tips] %<>% add(stats::rnorm(n.tips, mean=0, sd=jitter))
}
trees.f[[i]]
})
## plot all trees together
p <- ggtree(tr=trees.f[[1]], mapping=mapping, layout=layout, ...)
for (x in trees.f[-1])
p <- p + geom_tree(mapping=mapping, data=x, layout=layout, ...)
p
}
## wrapper for cohpenetic to ensure that branch lengths exist
.cophenetic.phylo.check.length <- function(tree, method) {
if (method != 'mds_dist' || is.null(tree$edge.length))
tree$edge.length <- rep(1, nrow(tree$edge))
stats::cophenetic(tree)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.