Modifying the tree
On some occasions, it can be useful to modify a phylogenetic tree, e.g. removing some clades or branches. TreeKnit offers a few methods for this:
prune!andprunesubtree!for pruning a clade.graft!for grafting a node onto a tree.insert!for inserting an internal node on an existing branch of a tree.delete!to delete an internal node while keeping the nodes below it.
Pruning
There are two functions to prune nodes: prune! and prunesubtree!. They behave exactly the same except for the return value: prune! returns the prunde clade as a Tree object, while prunesubtree! just returns its root as a TreeNode object. Both also return the previous ancestor of the pruned clade. Let's see an example
julia> tree = parse_newick_string("(A:1.,(B:1.,(X1:0.,X2:0.)X:5.)BX:1.)R;")____________ A _| | _____________ B |___________| | , X1 |______________________________________________________________| | X2
Let's assume that we realized leaves X1 and X2 are really a weird outlier in our tree. We want to get rid of them.
julia> tx, a = prune!(tree, "X1", "X2");julia> tx, X1 _| | X2julia> tree______________________________________ A _| |_____________________________________________________________________________ B
When called on a list of labels, prune! finds the MRCA of the input labels and prunes it from the tree. Here, lca(tree, X1, X2) is internal node X, which is removed from the tree. Note that cutting the branch above X will leave the internal node BX with a single child. By default, prune! also removes singletons from the input tree.
julia> map(label, nodes(tree)) # `BX` is not in there3-element Vector{String}: "B" "A" "R"
This behavior can be changed with the remove_singletons keyword argument:
julia> let tree = parse_newick_string("(A:1.,(B:1.,(X1:0.,X2:0.)X:5.)BX:1.)R;") prune!(tree, "X"; remove_singletons=false) map(label, nodes(tree)) end4-element Vector{String}: "B" "A" "BX" "R"
The prunesubtree! method does exactly the same as prune!, but returns the root of the pruned clade as a TreeNode, without converting it to a Tree. Thus the two calls are equivalent:
julia> tx = prune!(tree, "X")[1] # or ..., X1 _| | X2julia> tx = let r, a = prunesubtree!(tree, "X") node2tree(r) end, X1 _| | X2
Deleting a node
Grafting
Inserting a node
TreeNode level functions
All the methods above take a Tree as a first argument. As described in Basic concepts, the actual information about the tree is contained in TreeNode objects, while the Tree is basically a wrapper around TreeNodes. Thus, a method like prune! has to do two things:
- cut the ancestry relation between two
TreeNodeobjects. - update the
Treeobject in a consistent way.
The first part is where the "actual" pruning happens, and is done by the prunenode! function, which just takes a single TreeNode as input. TreeTools has similar "TreeNode level" methods (not exported):
prunenode!(n::TreeNode): cut the relation betweennand its ancestorgraftnode!(r::TreeNode, n::TreeNode): graftnontor.delete_node!(n::TreeNode): delete cut the relation betweennand its ancestora, but re-graft the children ofnontoa.insert_node!(c, a, s, t): inserts::TreeNodebetweencanda, at heightton the branch.
These methods only act on TreeNode objects and do not care about the consistency with the Tree. In most cases, it's more practical to call the Tree level methods. However, if speed is important, it might be better to use them.
Other useful functions
Remove singletons
remove_internal_singletons!