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 _| | X2
julia> 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 there
3-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)) end
4-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 _| | X2
julia> 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 TreeNode
s. Thus, a method like prune!
has to do two things:
- cut the ancestry relation between two
TreeNode
objects. - update the
Tree
object 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 betweenn
and its ancestorgraftnode!(r::TreeNode, n::TreeNode)
: graftn
ontor
.delete_node!(n::TreeNode)
: delete cut the relation betweenn
and its ancestora
, but re-graft the children ofn
ontoa
.insert_node!(c, a, s, t)
: inserts::TreeNode
betweenc
anda
, at heightt
on 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!