Sunday, June 4, 2017

General function for expanding (or contracting) the tip-spacing of subtrees during visualization

A couple of days ago a phytools user asked me about using custom tip-spacing in a fan-style tree plot in R. I posted a fairly straightforward solution; however how it works is not super obvious, and it involves a lot of scripting, so I thought it might be worth adding this as a function in phytools.

The idea with the function is not to plot a tree with custom tip-spacing - we can already do that using plotTree or plotSimmap. Rather, the function reorders the tree (if it is not in “cladewise” order), computes the desired spacing of each specified subtree, and then returns an object consisting of the tree & a vector that we can use in our tree plot.

Here's the function:

expand.clade<-function(tree,node,factor=5){
    cw<-reorder(tree)
    tips<-setNames(rep(1,Ntip(tree)),cw$tip.label)
    get.tips<-function(node,tree){
            dd<-getDescendants(tree,node)
            tree$tip.label[dd[dd<=Ntip(tree)]]
    }
    desc<-unlist(lapply(node,get.tips,tree=cw))
    for(i in 2:Ntip(cw)){
        tips[i]<-tips[i-1]+
            if(names(tips)[i]%in%desc){
                1 
            } else if(names(tips)[i-1]%in%desc){
                1
            } else 1/factor
    }
    obj<-list(tree=tree,tips=tips)
    class(obj)<-"expand.clade"
    obj
}

## S3 method for the object class
print.expand.clade<-function(x,...){
    cat("An object of class \"expand.clade\" consisting of:\n")
    cat(paste("(1) A phylogenetic tree (x$tree) with",Ntip(x$tree),
        "tips and\n   ",obj$tree$Nnode,"internal nodes.\n"))
    cat("(2) A vector (x$tips) containing the desired tip-spacing.\n\n")
}

Here's an example:

tree
## 
## Phylogenetic tree with 100 tips and 99 internal nodes.
## 
## Tip labels:
##  t4, t30, t47, t86, t87, t97, ...
## 
## The tree includes a mapped, 2-state discrete character with states:
##  a, b
## 
## Rooted; includes branch lengths.
nodes<-c(108,140,168)
for(i in 1:length(nodes)) tree<-paintSubTree(tree,nodes[i],"b","a")
obj<-expand.clade(tree,nodes,factor=2.5)
obj
## An object of class "expand.clade" consisting of:
## (1) A phylogenetic tree (x$tree) with 100 tips and
##     99 internal nodes.
## (2) A vector (x$tips) containing the desired tip-spacing.
plot(obj$tree,colors=setNames(c("blue","red"),c("a","b")),
    tips=obj$tips,type="fan",fsize=0.7,lwd=3)

plot of chunk unnamed-chunk-2

It also works for other tree styles. For instance:

obj<-expand.clade(tree,nodes,factor=3)
obj
## An object of class "expand.clade" consisting of:
## (1) A phylogenetic tree (x$tree) with 100 tips and
##     99 internal nodes.
## (2) A vector (x$tips) containing the desired tip-spacing.
plot(obj$tree,colors=setNames(c("blue","red"),c("a","b")),
    tips=obj$tips,fsize=0.4,lwd=1,ftype="off",xlim=c(0,6.5))
foo<-function(node,tree) extract.clade(tree,node)$tip.label
tips<-unlist(lapply(nodes,foo,tree=tree))
nn<-sapply(tips,function(tip,tree) which(tree$tip.label==tip),tree=tree)
linklabels(tips,nn,link.type="curved",cex=0.6)

plot of chunk unnamed-chunk-3

To get the final example to work, I had to push a fix to the function linklabels. The fix is already on GitHub & the functions of this post will be added soon.

That's it.

No comments:

Post a Comment