Saturday, February 13, 2021

On matching nodes between trees using matchNodes

Today I received an inquiry about the simple phytools utility function matchNodes.

matchNodes takes two trees as input and matches the node indices of the two trees using one of two criteria: the labels of the descendant tips of each node (method="descendants"); or the distances between matching tips (method="distances").

The inquiry was sent via a series of questions, but to paraphrase it here – the user essentially wanted to know if matchNodes could be used to identify corresponding nodes in a pair of trees in which the taxa present in each tree might be different – for instance, a pair of gene trees with slightly different taxa represented in each tree.

The answer is “yes & no.”

Effectively, matchNodes is not designed for this, but it's possible to think of an algorithm by which we could use matchNodes to accomplish this task.

First, let's make some data that have this property:

library(phytools)
## two identical trees
t1<-t2<-pbtree(n=26,tip.label=LETTERS)
## drop random tips from each tree
t1<-drop.tip(t1,sample(LETTERS,8))
t2<-drop.tip(t2,sample(LETTERS,8))
## give them random edge lengths
t1$edge.length<-runif(nrow(t1$edge))
t2$edge.length<-runif(nrow(t2$edge))
## compare them
plot(cophylo(t1,t2,rotate=FALSE))
nodelabels.cophylo()
nodelabels.cophylo(which="right")

plot of chunk unnamed-chunk-2

From this, we can see right away which nodes match. For instance, node 31 in tree 1 matches up with node 32 in tree 2; and node 23 in tree 1 matches with node 25 in tree 2. Neither of these pairing would be identified with matchNodes on either criterion.

matchNodes(t1,t2)
##       tr1 tr2
##  [1,]  19  NA
##  [2,]  20  NA
##  [3,]  21  NA
##  [4,]  22  22
##  [5,]  23  NA
##  [6,]  24  NA
##  [7,]  25  NA
##  [8,]  26  NA
##  [9,]  27  NA
## [10,]  28  NA
## [11,]  29  NA
## [12,]  30  30
## [13,]  31  NA
## [14,]  32  NA
## [15,]  33  NA
## [16,]  34  NA
## [17,]  35  NA

So, what's our solution. Well, what I propose is the following. First, we create two trees (let's say, tree 1' and tree 2') containing only taxa present in both of our original phylogenies.

Second, we use matchNodes method="descendants" to compare these two trees, one to the other.

Third, we compare these two trees (tree 1' and tree 2') back to the original trees using method="distances".

Finally, fourth we reconcile the node comparisons of steps two & three.

Let's try:

## step one drop tips
t1p<-drop.tip(t1,setdiff(t1$tip.label,t2$tip.label))
t2p<-drop.tip(t2,setdiff(t2$tip.label,t1$tip.label))

## step two match nodes "descendants"
M<-matchNodes(t1p,t2p)

## step two match nodes "distances"
M1<-matchNodes(t1,t1p,"distances")
M2<-matchNodes(t2,t2p,"distances")

## final step, reconcile
MM<-matrix(NA,t1$Nnode,2,
    dimnames=list(NULL,c("left","right")))
for(i in 1:nrow(MM)){
    MM[i,1]<-M1[i,1]
    nn<-M[which(M[,1]==M1[i,2]),2]
    if(length(nn)>0) MM[i,2]<-M2[which(M2[,2]==nn),1]
}
MM
##       left right
##  [1,]   19    19
##  [2,]   20    20
##  [3,]   21    21
##  [4,]   22    22
##  [5,]   23    25
##  [6,]   24    26
##  [7,]   25    NA
##  [8,]   26    27
##  [9,]   27    28
## [10,]   28    NA
## [11,]   29    NA
## [12,]   30    30
## [13,]   31    32
## [14,]   32    34
## [15,]   33    NA
## [16,]   34    NA
## [17,]   35    NA

Did we get it right?

plot(cophylo(t1,t2,rotate=FALSE))
nodelabels.cophylo()
nodelabels.cophylo(which="right")

plot of chunk unnamed-chunk-5

Yes! This just gives us the matches for every node in tree 1 to tree 2, if one exists. To get the inverse we just need to flip our last step:

## final step, reconcile
MM<-matrix(NA,t2$Nnode,2,
    dimnames=list(NULL,c("left","right")))
for(i in 1:nrow(MM)){
    MM[i,2]<-M2[i,1]
    nn<-M[which(M[,1]==M2[i,2]),1]
    if(length(nn)>0) MM[i,1]<-M1[which(M1[,2]==nn),1]
}
MM
##       left right
##  [1,]   19    19
##  [2,]   20    20
##  [3,]   21    21
##  [4,]   22    22
##  [5,]   NA    23
##  [6,]   NA    24
##  [7,]   23    25
##  [8,]   24    26
##  [9,]   26    27
## [10,]   27    28
## [11,]   NA    29
## [12,]   30    30
## [13,]   NA    31
## [14,]   31    32
## [15,]   NA    33
## [16,]   32    34
## [17,]   NA    35

Lastly, there is surely a more elegant solution to this problem available using ape::prop.part, or some other ape or phangorn functions – but the question was about matchNodes, so here's my answer!!

That's it.

Friday, December 18, 2020

Co-phylogenetic plotting with different color linking lines

I recently received the following inquiry:

“I am attempting to use phytools and cophyloplot to compare two trees. I would like to color the tip labels and connectors between certain tip labels. For example, in the tree below I might like to have 't1' and 't3' red in both trees and the connectors between them also be red. I have not been able to find an example for this in your blog. Could you tell me how this can be done or direct me to a blog post that explains this.”

In fact, this is pretty straightforward to do in phytools using the plot.cophylo argument link.col.

Here's a quick demo.

Load phytools:

library(phytools)

Load some co-phylogenetic data. This comes from Lopez-Vaamonde et al. (2001):

data(wasp.trees)
print(wasp.trees,details=TRUE)
## 2 phylogenetic trees
## tree 1 : 19 tips
## tree 2 : 15 tips
data(wasp.data)
wasp.data
##        Pleistodontes                Sycoscapter
## 1      P._greenwoodi             S._4_{obliqua}
## 2  P._xanthocephalus        S._2_{aff._obliqua}
## 3        P._plebejus   S._14_{hesperidiiformis}
## 4           P._rieki          S._12_{xylosycia}
## 5         P._blandus         S._11_{glandifera}
## 6         P._regalis         S._8_{pleurocarpa}
## 7          P._nitens          S._10_{crassipes}
## 8    P._schizodontes          S._9_{triradiata}
## 9      P._imperialis         S._15_{rubiginosa}
## 10      P._athysanus          S._1_{brachypoda}
## 11 P._astrabocheilus         S._7_{subpuberula}
## 12       P._proximus        S._6_{lilliputiana}
## 13    P._macrocainus         S._5_{cerasicarpa}
## 14       P._cuneatus            S._3_{playpoda}
## 15      P._froggatti S._australis_{macrophylla}

Now let's create & plot a standard "cophylo" object:

wasp.cophylo<-cophylo(wasp.trees[[1]],wasp.trees[[2]],
    assoc=wasp.data)
## Rotating nodes to optimize matching...
## Done.
wasp.cophylo
## Object of class "cophylo" containing:
## 
## (1) 2 (possibly rotated) phylogenetic trees in an object of class "multiPhylo".
## 
## (2) A table of associations between the tips of both trees.
plot(wasp.cophylo)

plot of chunk unnamed-chunk-3

That's pretty ugly. To get some more interesting colors for our linking lines, we can use the package randomcoloR as follows:

library(randomcoloR)
palette<-distinctColorPalette(nrow(wasp.data))

Let's graph these colors:

pie(rep(1,nrow(wasp.data)),col=palette)

plot of chunk unnamed-chunk-5

Now, our lines cross (obviously) so a useful feature of our colors could be to make them semi-transparent so that linking lines from one taxon to the other are easier to follow. Let's do that:

palette<-make.transparent(palette,0.5)

Finally, let's create our new "cophylo" plot:

par(lend=3)
plot(wasp.cophylo,link.col=palette,link.lwd=4,link.type="curved",
    link.lty="solid",fsize=c(0.8,0.8))

plot of chunk unnamed-chunk-7

That's all there is to it!