rphylogenyape-phylo

Add internal node to phylogenetic tree


I need to add an internal node in a phylogenetic tree, along one of the trees branches.

For example, let's say that I have the following tree:

library(ape)
library(phytools)

tree <- rtree(4)
plot(tree,no.margin=T,label.offset=0.1)
nodelabels()
tiplabels()
edgelabels()

enter image description here

I would like to insert a new node along branch 5 at time (edge.length) t, for example:

enter image description here

How could I do that?


Solution

  • You'll need to update the tree$edge table and the tree$edge.length vectors. You can find information on how these elements work in a "phylo" object here.

    Here's a quick function that should help you do do that on any tree:

    #@param tree a phylo object
    #@param element.IDs the numbers before and after the internal node
    #@param edge.length the added edge length
    add.internal.node <- function(tree, element.IDs, edge.length) {
    
        ## Find the edge in the edge table
        tree_edge <- tree$edge
        old_edge <- which(apply(tree_edge, 1, function(x, IDs) all(x == IDs), IDs = element.IDs))
        if(length(old_edge) == 0) {
            stop("edge not found")
        }
    
        ## Add the new edge
        tree_edge <- rbind(tree_edge[-old_edge, ], c(element.IDs[1], "new"), c("new", element.IDs[2]))
    
        ## Adding a new edge length
        if(!is.null(tree$edge.length)) {
            new_edge_lengths <- c(tree$edge.length, edge.length)
        }
    
        ## Re-ordering node labels for the new edge table
        ## (element IDs need to be ordered in phylo objects)
        #1- name the new node (should be ID+1)
        tree_edge <- gsub("new", paste0("new", element.IDs[1]+1), tree_edge)
        #2- update all the nodes after ID+1
        update.nodes <- function(x, ID) {
            if(!is.na(as.integer(x))) {
                ## Is not NA so it's a node (update the value if it's higher than the ID)
                return(ifelse(as.integer(x) > ID, as.integer(x)+1, x))
            } else {
                ## Is NA so it's the new node (leave it be for now)
                return(x)
            }
        }
        ## Update all nodes
        tree_edge <- apply(tree_edge, c(1,2), update.nodes, ID = element.IDs[1])
        ## Remove the "new" bit and make everything integers again
        tree_edge <- gsub("new", "", tree_edge)
        tree_edge <- apply(tree_edge, c(1,2), as.integer)
    
        ## Update the tree with the new node and the new edge.length
        tree$edge <- tree_edge
        if(!is.null(tree$edge.length)) {
            tree$edge.length <- new_edge_lengths
        }
        tree$Nnode <- tree$Nnode+1
    
        return(tree)
    }
    

    You can then add a node to your tree using the following pipeline:

    ## Generate a similar tree as in the example
    set.seed(20)
    tree <- rtree(4)
    plot(tree)
    axisPhylo(); nodelabels(); tiplabels(); edgelabels()
    
    ## Adding a node between elements 7 (node) and 4 (tip)
    test <- add.internal.node(tree, element.IDs = c(7, 4), edge.length = 0.1)
    plot(test)
    axisPhylo(); nodelabels(); tiplabels(); edgelabels()