ToyTree¶
The toytree.ToyTree
object is the main class in the toytree
package. It contains a number of useful functions for interacting with the underlying Node
structure (e.g., rooting, dropping tips), for storing and retrieving data (e.g., trait or support values), performing comparative or statistical analyses (e.g., tree distance metrics), and creating visualizations.
Note: this section of the documentation is relatively short since many of the other sections of the documentation are about properties or methods of ToyTree
objects.
import toytree
Generating ToyTrees¶
A ToyTree
is loaded in one of three ways: (1) by parsing tree data using toytree.tree()
(see tree/io); (2) by generating a fixed or random tree with toytree.rtree
(see rtree); or (3) by wrapping one or more toytree.Node
objects in a ToyTree
(see Building-trees-from-nodes). Follow the links for more details on each. We will use the random birth 8-tip unit tree generated below for this tutorial.
# generate a random uniform tree
tree = toytree.rtree.unittree(8, seed=123)
Trees are indexable and iterable¶
Node
objects can be selected from ToyTrees
in a variety of ways. See Node Query/selection for details.
tree[0]
<Node(idx=0, name='r0')>
tree[1:3]
[<Node(idx=1, name='r1')>, <Node(idx=2, name='r2')>]
for node in tree:
print(node)
<Node(idx=0, name='r0')> <Node(idx=1, name='r1')> <Node(idx=2, name='r2')> <Node(idx=3, name='r3')> <Node(idx=4, name='r4')> <Node(idx=5, name='r5')> <Node(idx=6, name='r6')> <Node(idx=7, name='r7')> <Node(idx=8)> <Node(idx=9)> <Node(idx=10)> <Node(idx=11)> <Node(idx=12)> <Node(idx=13)> <Node(idx=14)>
Cached Traversal¶
A ToyTree
stores a cached traversal of all Nodes in the tree in idxorder (see Traversal order/methods). Briefly, a traversal involves visiting each node in a tree exactly once. This is used to assign a unique integer label to every node. The idx labels 0-ntips
are the tips, and from ntips-nnodes-1
are the internal nodes. The nnodes-1
idx label corresponds to the root.
# show node idx labels of cached idxorder traversal
tree.draw('p');
The tree root (treenode)¶
A ToyTree
acts as a wrapper around a collection of connected Node
objects. Of these nodes, one has a special designation as the root of the tree, whether or not the tree structure is rooted or unrooted. This node merely represents the top of the hierarchical structure of stored nodes. This node can be accessed like any other node by indexing or by name, and can also be accessed as the .treenode
attribute of a ToyTree
.
# the root node
tree.treenode
<Node(idx=14)>
# also the root node
tree[-1]
<Node(idx=14)>
# the root has no parent Node (.up returns None)
tree.treenode.up
A rooted bifurcating tree has nnodes = (ntips * 2) - 1
, whereas an unrooted bifurcating tree has nnodes = (ntips * 2) - 2
. In other words converting from rooted to unrooted, or vice-versa, involves adding or removing a node from the tree. In an unrooted tree the root node is always a polytomy. (See the Tree rooting tutorial for further details.) This can be seen more clearly by plotting trees in an unrooted layout. The splits in the tree are the same regardless of the root node's placement. In the rooted tree (left) it is on a branch, and has two connected edges (a degree of 2). On the unrooted tree (right) it is on a node of the tree and connected to three edges (of degree 3).
toytree.mtree([tree, tree.unroot()]).draw(
layout='unr',
node_sizes=16,
node_labels="idx",
node_mask=(0,0,1),
);
Attributes¶
ToyTree
attributes store information about the size of the tree, and are automatically updated by the mod
functions when a tree structure is modified. The features
and edge_features
describe data stored to nodes of a tree and are further described in Data/Features.
tree.ntips
8
tree.nnodes
15
tree.treenode
<Node(idx=14)>
tree.features
('idx', 'name', 'height', 'dist', 'support')
tree.edge_features
{'dist', 'support'}
Methods¶
The ToyTree
object has a number of methods available directly from object, as well as many additional methods organized into subpackages that are also accessible from a tree.
# does the tree not contain any polytomies
tree.is_bifurcating()
True
# is the tree rooted
tree.is_rooted()
True
# query if set of tip nodes is monophyletic
tree.is_monophyletic("r3", "r2", "r1"), tree.is_monophyletic("r3", "r2", "r1", "r0")
(False, True)
# return a copy of the tree
tree.copy()
<toytree.ToyTree at 0x7ff02f48f520>
# return a tree drawing as a (Canvas, Cartesian, Mark)
tree.draw(ts='c')
(<toyplot.canvas.Canvas at 0x7ff02f4cb550>, <toyplot.coordinates.Cartesian at 0x7ff02f37feb0>, <toytree.drawing.src.mark_toytree.ToyTreeMark at 0x7ff02f37df30>)
# return all ancestors of query nodes
tree.get_ancestors("r4", "r5")
{<Node(idx=4, name='r4')>, <Node(idx=5, name='r5')>, <Node(idx=11)>, <Node(idx=12)>, <Node(idx=14)>}
# get dict mapping one feature to another
tree.get_feature_dict('idx', 'name')
{0: 'r0', 1: 'r1', 2: 'r2', 3: 'r3', 4: 'r4', 5: 'r5', 6: 'r6', 7: 'r7', 8: '', 9: '', 10: '', 11: '', 12: '', 13: '', 14: ''}
# get edges as a table
tree.get_edges(feature="idx", df=True)
child | parent | |
---|---|---|
0 | 0 | 8 |
1 | 1 | 8 |
2 | 2 | 9 |
3 | 3 | 9 |
4 | 4 | 11 |
5 | 5 | 11 |
6 | 6 | 13 |
7 | 7 | 13 |
8 | 8 | 10 |
9 | 9 | 10 |
10 | 10 | 12 |
11 | 11 | 12 |
12 | 12 | 14 |
13 | 13 | 14 |
# get mrca of a queried set of nodes
tree.get_mrca_node("r4", "r5")
<Node(idx=11)>
# get a DataFrame or Series of data from all nodes w/ formatting options
tree.get_node_data()
idx | name | height | dist | support | |
---|---|---|---|---|---|
0 | 0 | r0 | 0.00 | 0.25 | NaN |
1 | 1 | r1 | 0.00 | 0.25 | NaN |
2 | 2 | r2 | 0.00 | 0.25 | NaN |
3 | 3 | r3 | 0.00 | 0.25 | NaN |
4 | 4 | r4 | 0.00 | 0.50 | NaN |
5 | 5 | r5 | 0.00 | 0.50 | NaN |
6 | 6 | r6 | 0.00 | 0.75 | NaN |
7 | 7 | r7 | 0.00 | 0.75 | NaN |
8 | 8 | 0.25 | 0.25 | NaN | |
9 | 9 | 0.25 | 0.25 | NaN | |
10 | 10 | 0.50 | 0.25 | NaN | |
11 | 11 | 0.50 | 0.25 | NaN | |
12 | 12 | 0.75 | 0.25 | NaN | |
13 | 13 | 0.75 | 0.25 | NaN | |
14 | 14 | 1.00 | 0.00 | NaN |
# get mask that is True for selected nodes and False for others
tree.get_node_mask(0, 1, 2)
array([ True, True, True, False, False, False, False, False, False, False, False, False, False, False, False])
# get nodes matching a query
tree.get_nodes("~r[0-3]$")
[<Node(idx=2, name='r2')>, <Node(idx=3, name='r3')>, <Node(idx=0, name='r0')>, <Node(idx=1, name='r1')>]
# like get_node_data but only for tip (leaf) nodes
tree.get_tip_data()
idx | name | height | dist | support | |
---|---|---|---|---|---|
0 | 0 | r0 | 0.0 | 0.25 | NaN |
1 | 1 | r1 | 0.0 | 0.25 | NaN |
2 | 2 | r2 | 0.0 | 0.25 | NaN |
3 | 3 | r3 | 0.0 | 0.25 | NaN |
4 | 4 | r4 | 0.0 | 0.50 | NaN |
5 | 5 | r5 | 0.0 | 0.50 | NaN |
6 | 6 | r6 | 0.0 | 0.75 | NaN |
7 | 7 | r7 | 0.0 | 0.75 | NaN |
# return the names for nodes idx 0-ntips in order
tree.get_tip_labels()
['r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7']
# return a hash str unique to this topology
tree.get_topology_id()
'535ff2bd575ed1f753d5b5f6340e8389'
Modifying trees¶
See the mod
documentation section for details on tree modification methods. Many methods are available for modifying the topology, branch lengths, or other data on trees. These include common options like .ladderize
, .prune
, .root
, .collapse_nodes
, edges_set_node_heights
, and many more. These functions are written to be very efficient, requiring the minimum number of tree traversals or modifications, and ensure the returned tree has proper node idx, height, and other node features updated.
# extract a subtree connecting tips r0, r1, r2, r3 and draw it
toytree.mod.extract_subtree(tree, "~r[0-3]").draw();
Chaining methods¶
Many methods of a ToyTree
return a copy of the tree which are intended to make it easy to chain together multiple function calls to accomplish complex operations. For example, below the tree is re-rooted, scaled to a new crown height, ladderized, adds an internal node name, and then calls draw with many styling options including showing the new node name.
# example of complex chained function calls
canvas, axes, mark = (tree
.root("r6")
.mod.edges_scale_to_root_height(10)
.ladderize(True)
.set_node_data("name", {-1: 'root'})
.draw(
node_labels="name", node_mask=(0, 0, 1), node_sizes=18,
node_markers="r2x1", node_colors="lightgrey",
scale_bar=True, tip_labels_align=True,
)
);
Subpackage API¶
Many functions for working with trees are organized in subpackages. See their documentation sections for more details. Any of these methods which accept a toytree as their first argument are also accessible from a ToyTree
object as a convenience. For example, toytree.mod.func(tree)
is equivalent to tree.mod.func()
. We refer to this as using the module-level API versus the object-level API.
# call drop_tips from the module-level
toytree.mod.drop_tips(tree, "~r[4-6]").draw();
# call drop_tips from the object-level
tree.mod.drop_tips("~r[4-6]").draw();
Writing trees¶
See the Writing tree data documentation section. The function write()
is used to write a tree to a newick or other format of serialized tree data, and to return it as a string or write to a file from a ToyTree
, with options to also include metadata.
tree.write()
'((((r0:0.25,r1:0.25):0.25,(r2:0.25,r3:0.25):0.25):0.25,(r4:0.5,r5:0.5):0.25):0.25,(r6:0.75,r7:0.75):0.25);'
Drawing ToyTrees¶
See the Tree Drawing documentation section for a detailed description of drawing options using the .draw()
function.
Style dict¶
ToyTree
objects have a .style
dict-like object that can be used to view all options for styling tree drawings, and to modify their default drawing style. This is an optional convenience, it is often simpler to provide arguments directly to the .draw()
function.
# create a copy that we will modify the .style of
tre = tree.copy()
# set several style options on the tree
tre.style.node_style.fill = "black"
tre.style.node_style.stroke = "white"
tre.style.node_style.stroke_width = 3
tre.style.node_sizes = 12
tre.style.node_mask = False
tre.style.node_markers = "v"
tre.style.edge_style.stroke = "darkcyan"
tre.style.edge_style.stroke_width = 3
tre.style.layout = 'd'
# call draw and the .style will form the default tree style
tre.draw(tree_style=None);