Thursday, June 9, 2022

How to plot tip & node labels without tiplabels or nodelabels

A recent tweet from Andrea Berardi asked how to adjust offset of pie graphs or circles plotted as tip labels in R.

Lots of others responded with excellent suggestions, but I nonetheless thought it might be handy to provide another, alternative solution that doesn't use ape::nodelabels or ape::tiplabels at all!

You can find similar tips & tricks in my upcoming book with Luke Harmon.

First, I'm going to pull an example. These are data for diel activity pattern in primates from Kirk & Kay (2004) – that we also use in our book, by the way!

library(phytools)
## load tree
primate.tree<-read.tree(file="http://www.phytools.org/Rbook/3/primateEyes.phy")
primate.tree
## 
## Phylogenetic tree with 90 tips and 89 internal nodes.
## 
## Tip labels:
##   Allenopithecus_nigroviridis, Cercopithecus_mitis, Cercopithecus_cephus, Cercopithecus_petaurista, Erythrocebus_patas, Chlorocebus_aethiops, ...
## 
## Rooted; includes branch lengths.
## load data
primate.data<-read.csv(file="http://www.phytools.org/Rbook/3/primateEyes.csv",
    row.names=1,stringsAsFactors=TRUE)
head(primate.data)
##                                  Group Skull_length Optic_foramen_area
## Allenopithecus_nigroviridis Anthropoid         98.5                7.0
## Alouatta_palliata           Anthropoid        109.8                5.3
## Alouatta_seniculus          Anthropoid        108.0                8.0
## Aotus_trivirgatus           Anthropoid         60.5                3.1
## Arctocebus_aureus            Prosimian         49.5                1.2
## Arctocebus_calabarensis      Prosimian         53.8                1.6
##                             Orbit_area Activity_pattern
## Allenopithecus_nigroviridis      298.7          Diurnal
## Alouatta_palliata                382.3          Diurnal
## Alouatta_seniculus               359.4          Diurnal
## Aotus_trivirgatus                297.4        Nocturnal
## Arctocebus_aureus                134.8        Nocturnal
## Arctocebus_calabarensis          156.6        Nocturnal
##                             Activity_pattern_code
## Allenopithecus_nigroviridis                     0
## Alouatta_palliata                               0
## Alouatta_seniculus                              0
## Aotus_trivirgatus                               2
## Arctocebus_aureus                               2
## Arctocebus_calabarensis                         2
activity.pattern<-setNames(primate.data$Activity_pattern,rownames(primate.data))
head(activity.pattern)
## Allenopithecus_nigroviridis           Alouatta_palliata 
##                     Diurnal                     Diurnal 
##          Alouatta_seniculus           Aotus_trivirgatus 
##                     Diurnal                   Nocturnal 
##           Arctocebus_aureus     Arctocebus_calabarensis 
##                   Nocturnal                   Nocturnal 
## Levels: Cathemeral Diurnal Nocturnal

Neat.

(As an extra step here, which sometimes helps with plotting methods later, I'm going to run the function phytools::untangle. This has the effect of reordering the tip labels of the tree in what ape calls “cladewise” fashion, and can be pretty useful. This is not necessary here, because our tree was written in simple Newick format – but can be critical when we read trees from NEXUS format with a translation table!)

primate.tree<-untangle(primate.tree,"read.tree")

Next, I'm going to run stochastic character mapping, using the ARD model.

simmap.trees<-make.simmap(primate.tree,activity.pattern,model="ARD",
    pi="fitzjohn",nsim=100)
## make.simmap is sampling character histories conditioned on
## the transition matrix
## 
## Q =
##              Cathemeral      Diurnal    Nocturnal
## Cathemeral -0.050529521  0.050529521  0.000000000
## Diurnal     0.002306398 -0.004367451  0.002061052
## Nocturnal   0.002586387  0.003565036 -0.006151422
## (estimated using likelihood);
## and (mean) root node prior probabilities
## pi =
##  Cathemeral     Diurnal   Nocturnal 
## 0.003675779 0.021703763 0.974620458
## Done.

Here are my stochastic character mapped trees:

par(mfrow=c(10,10))
cols<-setNames(c("brown","goldenrod","black"),levels(activity.pattern))
plot(simmap.trees,cols,ftype="off",lwd=1)

plot of chunk unnamed-chunk-4

par(mfrow=c(1,1))

Already, now let's compute the marginal frequencies at all the internal nodes of the tree using summary.

obj<-summary(simmap.trees)
plot(obj,colors=cols,ftype="i",fsize=0.7)
legend("bottomleft",levels(activity.pattern),pch=21,pt.bg=cols,
    pt.cex=2,bty="n")

plot of chunk unnamed-chunk-5

I mean, this is OK, but we actually have limitless ability to add graphical elements to a plotted phylogeny in R, as long as we know where in our plot to add them!

To get this information, we want to pull the object last_plot.phylo from the R environment .PlotPhyloEnv. It doesn't matter if you don't really know what that means, or even what an environment is in R, you can still take advantage of this trick.

To start we'll re-plot our phylogeny, and then leave this graphical device open.

plotTree(primate.tree,ftype="i",fsize=0.5,lwd=1,offset=0.5)

plot of chunk unnamed-chunk-6

pp<-get("last_plot.phylo",envir=.PlotPhyloEnv)

This object that we just pulled, contains (among other things) the x and y coordinates of all the nodes and tips of the phylogeny!

str(pp)
## List of 20
##  $ type           : chr "phylogram"
##  $ use.edge.length: logi TRUE
##  $ node.pos       : num 1
##  $ show.tip.label : logi TRUE
##  $ show.node.label: logi FALSE
##  $ font           : num 3
##  $ cex            : num 0.5
##  $ adj            : num 0
##  $ srt            : num 0
##  $ no.margin      : logi FALSE
##  $ label.offset   : num 0.5
##  $ x.lim          : num [1:2] 0 90.5
##  $ y.lim          : num [1:2] 1 90
##  $ direction      : chr "rightwards"
##  $ tip.color      : chr "black"
##  $ Ntip           : int 90
##  $ Nnode          : int 89
##  $ edge           : int [1:178, 1:2] 91 92 93 94 95 96 97 97 98 99 ...
##  $ xx             : num [1:179] 73 73 73 73 73 ...
##  $ yy             : num [1:179] 1 2 3 4 5 6 7 8 9 10 ...

To see that this is true, let's just plot simple points at all the nodes and tips as follows:

points(pp$xx,pp$yy)

plot of chunk unnamed-chunk-9

Now, let's attempt the simple task of setting the colors of the points shown at the tips by the states of our discrete character for each tip.

h<-max(nodeHeights(primate.tree))
plotTree(primate.tree,ftype="i",fsize=0.5,lwd=1,offset=0.5)
points(pp$xx[1:Ntip(primate.tree)]+0.01*h,pp$yy[1:Ntip(primate.tree)],pch=16,
    col=cols[activity.pattern[primate.tree$tip.label]])

plot of chunk unnamed-chunk-10

Let's add pie charts to the nodes. We're not going to do this, however, using ape::nodelabels. Instead, we'll use plotrix::floating.pies. plotrix is a dependency of phytools, so you already have it installed.

for(i in 1:primate.tree$Nnode){
    plotrix::floating.pie(pp$xx[i+Ntip(primate.tree)],
        pp$yy[i+Ntip(primate.tree)],radius=0.015*h,
        x=obj$ace[i,],col=cols,border="transparent")
}

plot of chunk unnamed-chunk-12

Finally, add a legend.

legend("bottomleft",levels(activity.pattern),pch=16,col=cols,
    pt.cex=2,bty="n")

plot of chunk unnamed-chunk-14

Just to see how this gives us more flexibility, here's an example in which I offset the tip labels, and then also adjust the internal node label size depending on whether or not there's uncertainty (max. PP < 0.99) for each state.

h<-max(nodeHeights(primate.tree))
plotTree(primate.tree,ftype="i",fsize=0.5,lwd=1,offset=0.5)
points(pp$xx[1:Ntip(primate.tree)]+0.01*h,pp$yy[1:Ntip(primate.tree)],pch=16,
    col=cols[activity.pattern[primate.tree$tip.label]])
for(i in 1:primate.tree$Nnode){
    plotrix::floating.pie(pp$xx[i+Ntip(primate.tree)],
        pp$yy[i+Ntip(primate.tree)],
        radius=if(max(obj$ace[i,]^2)<0.98) 0.02*h else 0.01*h,
        x=obj$ace[i,],col=cols,border="transparent")
}
legend("bottomleft",levels(activity.pattern),pch=16,col=cols,
    pt.cex=2,bty="n")

plot of chunk unnamed-chunk-15

That's alright.

3 comments:

  1. Hello Dr. Revell,
    Thank you for posting this solution. I tried your data and code in my system, but got an error during running

    “simmap.trees<-make.simmap(primate.tree,activity.pattern,model="ARD",
    pi="fitzjohn",nsim=100)”
    “Error in sum(pi) : invalid 'type' (character) of argument”

    Do you have any idea about how I can fix it? Thank you very much!

    ReplyDelete

Note: due to the very large amount of spam, all comments are now automatically submitted for moderation.