liamrevell / phytools

GNU General Public License v3.0
198 stars 56 forks source link

consensus.edges() produces only (!) unrooted trees (with solution) #123

Closed AEgit closed 1 year ago

AEgit commented 1 year ago

The implementation of consensus() in ape changed from version 5.5 to 5.6 adding the new parameter rooted with default setting FALSE (see: https://github.com/cran/ape/commit/10898aebdf6661a0b81ba21bf24969336b544a60#diff-39f99c034d3383f59d20b396e9d7fa009fabd32db58ab40ff1e284a6a8468c01). This has implications for the phytools function consensus.edges(), which relies on the consensus() function of ape. Due to this change all consensus trees produced by consensus.edges() are unrooted (!). This behaviour is not desirable and differs from the previous behaviour of the function, which produced rooted consensus trees for (rooted) input trees.

Solution: Add the argument rooted to the function consensus.edges().

The code below exemplifies the problem and provides the solution (with the attached updated consensus.edges() function, that adds the new argument rooted):

library(phytools)

set.seed(5)
# #Generate random tree
mytree <- rtree(10)
plot(mytree)
# #Generate second tree, which we will use to swap around some of the tip labels (for consensus function)
mytree2 <- mytree
mytree2$tip.label
mytree2$tip.label[c(3, 5, 6, 9)] <- mytree$tip.label[c(5, 3, 9, 6)] # #Shuffle around a few tip labels to see effect of consensus() function
plot(mytree2)

# #Depending on which ape version is installed, this tree is either rooted (ape 5.5) or unrooted (ape 5.6.2)
plot(consensus(c(mytree, mytree2), p=0.5))
# #vs.
plot(consensus(c(mytree, mytree2), p=0.5, rooted = TRUE)) # #Note, that the rooted argument is not available in ape 5.5
# #See: https://github.com/cran/ape/commit/10898aebdf6661a0b81ba21bf24969336b544a60#diff-39f99c034d3383f59d20b396e9d7fa009fabd32db58ab40ff1e284a6a8468c01
# #ape version 5.6.0 introduced the argument "rooted" to the consensus function and set the default to "FALSE"
# #This means that all input trees for the consensus function are treated as unrooted

# #This has implications for consensus.edges() which uses the consensus() function of ape with the default setting
plot(consensus.edges(c(mytree, mytree2), method = "mean.edge"))

# #Prior to the changes in ape, consensus() and consensus.edges() would generate a rooted consensus tree for rooted input trees
# #Now this is only possible for consensus() if the default for rooted is changed to "TRUE"
# #consensus.edges() only (!) produces unrooted trees -> this new behaviour differs from the previous behaviour and is not desirable

# #Suggested solution: Add argument rooted to consensus.edges()
source("consensus.edges.root")
consensus.edges.root
plot(consensus.edges.root(c(mytree, mytree2), method = "mean.edge", rooted = FALSE)) # #Generate unrooted consensus tree
plot(consensus.edges.root(c(mytree, mytree2), method = "mean.edge", rooted = TRUE)) # #Generate rooted consensus tree (previous behaviour)

# #P.S.: The old consensus() function did not provide node labels, the new one calculates them using bs/ntree (see definition of
# #consensus() function for more details) -> this obviously also happens for the consensus.edges() function. But I reckon, people
# #will not be bothered by this behaviour

Updated consensus.edges.root function: consensus.edges.root.txt

liamrevell commented 1 year ago

Hi @AEgit. Thanks for bringing this to my attention. I believe it is now fixed. Please try updating phytools from GitHub and let me know if you find this function is working again as it should. (Thanks also for the pull request. I ended up adding an optional argument, rooted, as you suggested, but also with an internal check to set its value.) -- Liam

AEgit commented 9 months ago

I took ages to reply, but here I go: Thanks for fixing the problem!