Modifying tree topologies with mod
¶
The submodule toytree.mod
includes many options for efficiently modifying a tree. In this section we describe methods for modifying the topology -- i.e., the relationships among nodes. While it is possible to accomplish this by editing attributes of the Node objects themselves, such as .up
and .children
, this can be error-prone if you are not highly experienced. Instead, the we provide a suite of methods for common topology manipulations that are intended to work efficiently and to retain other information such as node and edge features correctly. You can find these methods listed in the table of contents to the right. Below we describe examples of each of these methods.
import toytree
# example tree for demonstrations
tree = toytree.rtree.unittree(ntips=6, seed=123)
tree.draw();
Module-level versus Object-level APIs¶
The methods in toytree.mod
can also be called directly from ToyTree objects using the object-level API.
# module level API takes tree as the first arg
rtree = toytree.mod.root(tree, "r4")
# object-level API knows the tree is the object to operate on
rtree = tree.mod.root("r4")
mod_tree = toytree.mod.add_internal_node(tree, "r2", dist=0.1, name="X")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="name");
add_child_node¶
This function selects an existing Node to act as the parent, and
inserts a new Node as a child of that parent. The new child's name
and dist can be set. If no dist value is entered then the child's
dist is set to match that of its new sister Nodes, or 1.0.
Unless a unary internal node already exists (e.g., you call add_internal_node
) adding
an child to an existing internal node will typically create a polytomy. Thus this method
may often be paired with add_child_node
.
mod_tree = toytree.mod.add_child_node(tree, 6, name="X")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
add_sister_node¶
This function selects an existing Node to act as the sister, and
inserts a new Node as a child of the same parent. The new sister
Node's name and dist can be set. If no dist value is entered then
the its dist is set to match the max dist of its sister Nodes or 1.
This function is equivalent to :func:.add_child_node
but the
query is used to select a sister rather than a parent.
mod_tree = toytree.mod.add_sister_node(tree, "r3", name="X")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
add_internal_node_and_child¶
Splits a branch spanning from query node (A) to its parent (B)
to create a new internal Node (C) and child Node (D). The new
parent and child Nodes can be given names and dist values. If
no value is entered for parent_dist
then the parent Node is
inserted at the midpoint of the edge. If a parent_dist value is
entered then it must fit within the length of the query Node's
dist or an error is raised. The new child Node dist is not
constrained. If no value is entered then it will be automatically
set to match the dist of its sister Node.
mod_tree = toytree.mod.add_internal_node_and_child(tree, "r3", name="C", parent_name="P")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="name");
add_internal_node_and_subtree¶
Add a subtree by splitting an edge to create a new parent Node and inserting the subtree as a child (i.e., tree-grafting). By default, if left as None, the subtree stem dist will be set to half the distance to the farthest leaf, and the subtree will be scaled to fill the other half distance so that it aligns as the farthest tip node distance.
# get a subtree from this tree or a different one
subtree = tree.mod.extract_subtree("r0", "r1", "r2", "r3")
# insert this subtree at a selected node in the this tree
mod_tree = toytree.mod.add_internal_node_and_subtree(tree, "r4", subtree=subtree, subtree_rescale=True, parent_name="P")
# draw
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="name", height=300);
remove_nodes¶
If multiple Nodes are entered they are removed in a postorder traversal of the tree. Nodes can be selected using Node Queries.
mod_tree = toytree.mod.remove_nodes(tree, "r1", "r2", "r3", )
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
remove_unary_nodes¶
Some operations can leave behind "unary nodes" which only have a single child Node instead of the typical pattern in a tree of having 2 or more children. You could find and remove these nodes using remove_nodes
, but another option to use remove_unary_nodes
which will search for and remove these nodes for you.
# get a tree with a unary node
mod_tree1 = toytree.mod.add_internal_node(tree, "r2", name="X")
# remove the unary node
mod_tree2 = toytree.mod.remove_unary_nodes(mod_tree1)
toytree.mtree([mod_tree1, mod_tree2]).draw(node_sizes=15, node_labels="idx");
merge_nodes¶
The method merge_nodes
can be used to remove nodes from a tree based on a Callable function that return True or False. The specified "merge_method" function will be performed at each Node in the tree, and if it returns True the node will be collapsed. A second function "selection_method" can be used to select which node will be retained. The default is to select the Node with the lowest idx (using the min function).
# create a mod tree with two sister nodes labeled 'r1'
mod_tree1 = tree.mod.add_internal_node_and_child("r1", name="r1")
# merge nodes with identical names
mod_tree2 = toytree.mod.merge_nodes(mod_tree1, merge_method="name", )
toytree.mtree([mod_tree1, mod_tree2]).draw(node_sizes=15, node_labels="idx");
collapse_nodes¶
Nodes can be entered as Node instances, Node names strings, or Node int idx labels, and/or Nodes can be selected by minimum dist or support values. Selected Nodes are collapsed into multi-furcating polytomies. For example, set min_support=50 to collapse all nodes with support < 50, and/or select Node idx 10 to collapse Node 10.
mod_tree = toytree.mod.collapse_nodes(tree, 7)
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
rotate_nodes¶
Return ToyTree with one Node rotated (children order reversed). Rotates only one Node per call. Internal Nodes can be selected by idx label, or by entering multiple tip Node names from which the MRCA will be selected and rotated.
mod_tree = toytree.mod.rotate_node(tree, 8)
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
Subtree-level modification¶
prune¶
The prune()
method returns a tree with a subset of queried Nodes along with the minimal spanning edges required to connect the Nodes. Nodes can be queried as individual arguments or as a set of indices, e.g. prune([0,1,2]). When called on a rooted tree, the user can require the originial root to be retained in the pruned tree using require_root=True
. By default, this is False and the lowest MRCA connecting the queried Nodes will instead be kept as the new root. When internal Nodes are discarded by prune
their distances will be merged into the distance of the queried Node such that the original distance between the root and the queried Node remains the same. If preserve_dists=False
, then only the original distances assigned to the queried Nodes are retained.
mod_tree = toytree.mod.prune(tree, "r0", "r1", "r2")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx", scale_bar=True);
drop_tips¶
Return a ToyTree with some tip Nodes removed. The ToyTree with the selected tip Nodes (and any remaining internal
nodes without children) are removed while retaining the original
edge lengths between remaining nodes. This is effectively the
inverse of prune
. Tip names can be selected using a Query method
of Node instances, Node names, or Node idx int labels. Only
selected tip Nodes affect the result.
mod_tree = toytree.mod.drop_tips(tree, "r0", "r1", "r2")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx", scale_bar=True);
extract_subtree¶
Return a subtree/clade extracted from a larger tree as a ToyTree. It will find the MRCA of the queried nodes and return a tree representing this subclade.
mod_tree = toytree.mod.extract_subtree(tree, "r0", "r3")
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx", scale_bar=True);
bisect¶
The bisect()
method returns a tree bisected into two subtrees on a selected edge. This edge is given by the edge above a queried Node.
When used on a rooted tree, querying the TreeNode returns a subtree for each child as its own TreeNode with its original distance value. When used on an unrooted tree, the TreeNode cannot be queried. When any other Node is queried, it will split the edge above it to create two subtrees with the queried Node bing a TreeNode of one new subtree. Including the argument reroot=True
will cause the Node above the query to become the new TreeNode in its subtree, otherwise the original TreeNode will be retained.
sub1, sub2 = toytree.mod.bisect(tree, 6)
toytree.mtree([tree, sub1, sub2]).draw(node_sizes=15, node_labels="idx", scale_bar=True);
Tree-level modification¶
resolve_polytomies¶
resolve_polytomies()
can be used to randomly resolve polytomies in a tree. It accepts options to set defaults dist and support values on resolved nodes, and can accept a random seed for reproducible results.
mod_tree1 = toytree.mod.collapse_nodes(tree, 7, 6)
mod_tree2 = toytree.mod.resolve_polytomies(mod_tree1, dist=0.1)
toytree.mtree([mod_tree1, mod_tree2]).draw(node_sizes=15, node_labels="idx");
ladderize¶
ladderize()
formats a tree such that a Node's right/lower child always has more descendants than its left/upper child. The user can also choose to order it in reverse order such that the left/upper child has more descendants by using the argument direction=True
.
mod_tree = toytree.mod.ladderize(tree, direction=False)
toytree.mtree([tree, mod_tree]).draw(node_sizes=15, node_labels="idx");
Rooting/Unrooting¶
Several methods are available for rooting trees. Please see the separate rooting trees documentation page.