MultiTree objects

Toytree supports the use of MultiTree objects to store lists of linked trees, such as bootstrap replicates or trees sampled from a posterior distribution. MultiTree objects can be generated from a list of Toytrees or newick strings, or by parsing a file, url, or string of text that includes newick trees separated by newlines. The convenience function for creating MuliTrees is toytree.mtree().

[1]:
import toytree
import toyplot
import numpy as np

Parsing data into MultiTrees

An example string or file representing multiple trees as newick strings:

[2]:
string = """\
(((a:1,b:1):1,(d:1.5,e:1.5):0.5):1,c:3);
(((a:1,d:1):1,(b:1,e:1):1):1,c:3);
(((a:1.5,b:1.5):1,(d:1,e:1):1.5):1,c:3.5);
(((a:1.25,b:1.25):0.75,(d:1,e:1):1):1,c:3);
(((a:1,b:1):1,(d:1.5,e:1.5):0.5):1,c:3);
(((b:1,a:1):1,(d:1.5,e:1.5):0.5):2,c:4);
(((a:1.5,b:1.5):0.5,(d:1,e:1):1):1,c:3);
(((b:1.5,d:1.5):0.5,(a:1,e:1):1):1,c:3);
"""

Create a MultiTree object by passing the input data to toytree.mtree(). The treelist attribute of mtree objects provides a list of the trees in it. These can be indexed like any list and each individual item is a ToyTree, which can be drawn or manipulated like any normal ToyTree class object.

[3]:
# create an mtree from a string, list of strings, url, or file.
mtre0 = toytree.mtree(string)

# access the treelist
mtre0.treelist
[3]:
[<toytree.Toytree.ToyTree at 0x7fbd8c012da0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa05f8>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa08d0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa0ac8>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa0cc0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa0eb8>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfb60f0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfb62e8>]
[4]:
# create an mtree from a list of ToyTrees (can be sliced like any list)
mtre1 = toytree.mtree(mtre0.treelist[:5])

# access the treelist
mtre1.treelist
[4]:
[<toytree.Toytree.ToyTree at 0x7fbd8c012da0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa05f8>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa08d0>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa0ac8>,
 <toytree.Toytree.ToyTree at 0x7fbd8bfa0cc0>]
[5]:
# access an individual toytree from the treelist
mtre1.treelist[0].draw();
edbac

Consensus trees

Before we get into the plotting features of MultiTrees, let’s first explore several useful functions that toytree provides for analyzing groups of trees. First, we can infer a majority-rule consensus tree from a group of input topologies. The .get_consensus_tree function will return a ToyTree with the consensus topology and clade supports stored on nodes as the “support” feature.

[6]:
ctre = mtre0.get_consensus_tree().root("c")
ctre.draw(node_labels='support', use_edge_lengths=False, node_sizes=16);
7575100baedc

Access ToyTrees in the treelist

You can access each item in a treelist and plot it to examine the variation in topologies individually. Or you can do any other calculations you wish using the underlying TreeNode objects. Below we iterate over all toytree objects in the treelist and extract the underlying TreeNode and pass each to the consensus tree’s TreeNode object which has a function to calculate Robinson-Foulds distances. This is a measure of the topological mismatch between the trees.

[7]:
# get Robinson-Foulds distances between consensus and each tree in list
[ctre.treenode.robinson_foulds(i.treenode)[0] for i in mtre0.treelist]
[7]:
[0, 4, 0, 0, 0, 0, 0, 4]
[8]:
# iterate over treelist and plot each tree on a separate canvas
for tre in mtre0.treelist[:4]:
    tre.draw()
edbac
ebdac
edbac
edbac

TreeGrid plot

A bit simpler still when you call .draw() from a multitree it will return a grid drawing with multiple toytrees spaced on a canvas. A similar plot an be made by using toyplot Canvas and Axes arguments, as explained in the Quick Guide, but this is a quick shortcut for examining a number of trees.

[9]:
mtre0.draw(nrows=2, ncols=4, ts='o');
edbacebdacedbacedbacedbacedabcedbaceadbc

Fixing the tip order

With MultiTree plots the goal is often to view discordance among trees, which can be made more apparent by fixing the tip order so it is the same in all trees. You can fix the tip order for a toytree drawing by using the fixed_order argument, and similarly for multitree drawings this will fix the order across multiple tree drawings. By default the spacing between tips is 1, so when you provide a list of five names it will arrange them onto the coordinates (0, 1, 2, 3, 4). Below I show the axes so you can see the tip placement more clearly.

[10]:
# drop the first tree with fixed_order
canvas, axes, mark = mtre0.treelist[1].draw(
    fixed_order=['e', 'd', 'b', 'a', 'c'],
    ts='o',
);

# show the axes
axes.show = True
ebdac-3-2-10024

Similarly, you can also set an explicit spacing between tips using the fixed_position argument to draw. This is less often used since we usually want the tips to be spaced evenly, but there are cases where you can create interesting drawings by providing a list of tip labels in fixed_order and a corresponding list of positions in fixed_position.

[11]:
canvas, axes, mark = mtre0.treelist[1].draw(
    fixed_order=['e', 'd', 'b', 'a', 'c'],
    fixed_position=[0, 1, 3, 6, 12],
    edge_type='c',
);
axes.show = True
ebdac-3-2-1004812

This applies similarly to multitree drawings. In the example below I use a boolean True argument to fixed_order which will infer the consensus tree tip order and use that to order the tip labels for all of the tree drawings.

[12]:
mtre0.draw(nrows=2, ncols=4, ts='o', fixed_order=True);
edbacebdacedbacedbacedbacedabcedbaceadbc

CloudTree plot

It can be more informative still to plot a number of trees on top of each other. These are sometimes called “densitree” plots, or here, “cloud tree plots”.

[13]:
# draw cloud tree
canvas, axes, mark = mtre0.draw_cloud_tree(
    edge_style={
        "stroke-opacity": 0.1,
        "stroke-width": 3,
    },
);
edbac

Styling tip labels in cloud trees

In cloud tree plots a fixed order of the tips will always be enforced, which allows for the discordance among trees to be visualized. Because each tree within the multitree object may have different ordering of it’s tips, we only print the tip labels once. The order of the tips of the tree can be changed by using the fixed order argument, otherwise a consensus tree is quickly inferred and used for the tip order. To style the tip labels or change them, like below, you can provide a list of new names in the same order as in the first tree in the treelist.

[14]:
# draw cloud tree (here with some extra styling)
mtre0.draw_cloud_tree(
    width=250,
    fixed_order=['c', 'd', 'e', 'b', 'a'],
    edge_style={"stroke-opacity": 0.1, "stroke-width": 2},
    tip_labels=["tip-{}".format(i) for i in mtre0.treelist[0].get_tip_labels()],
);
tip-etip-dtip-btip-atip-c

Example: Xiphophorus fishes

Data set for reconstructing a densitree figure from Cui et al. (2013). I’ve taken the nexus file from the paper’s dryad repository and converted it to newick and saved it online so it can be easily downloaded. The file contains 160 trees representing mrbayes consensus trees inferred for different genomic regions.

[15]:
fish = toytree.mtree("https://eaton-lab.org/data/densitree.nex")
print(len(fish))
160

Tree grid styling

Trees in TreeGrid drawings can be styled individually by setting the style dictionary attribute of each ToyTree in the treelist. Additionally, most styles can be applied as arguments to the draw_tree_grid() function to apply styles to all trees at once.

[16]:
fish.draw(nrows=1, ncols=4, height=300);
Xmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXhelleriiXsignumXmonticolusXclemenciae_GXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXxiphidiumXandersiXmaculatus_JpWildPsjonesiiPriapellaXnezahuacoyotlXcorteziXbirchmanni_GARCXmalinche_CHIC2XmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmeyeriXgordoniXcouchianusXxiphidiumXvariatusXandersiXmilleriXevelynaeXmaculatus_JpWildXclemenciae_GXmonticolusXsignumXmayaeXhelleriiXalvareziPsjonesiiPriapellaXmeyeriXgordoniXcouchianusXxiphidiumXandersiXvariatusXevelynaeXmilleriXmaculatus_JpWildXclemenciae_GXmonticolusXmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiPsjonesiiPriapellaXnezahuacoyotlXcorteziXmalinche_CHIC2Xbirchmanni_GARCXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiXclemenciae_GXmonticolusXxiphidiumXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXandersiXmaculatus_JpWildPsjonesiiPriapella
[17]:
# make a copy of the multitree since we will modify the style of each tree
cfish = fish.copy()

# set different 'tip_labels_colors' for each tree
for tree in cfish.treelist[:4]:
    tree.style.tip_labels_colors = next(toytree.icolors2)

# draw several trees
cfish.draw(1, 4, height=300);
Xmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXhelleriiXsignumXmonticolusXclemenciae_GXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXxiphidiumXandersiXmaculatus_JpWildPsjonesiiPriapellaXnezahuacoyotlXcorteziXbirchmanni_GARCXmalinche_CHIC2XmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmeyeriXgordoniXcouchianusXxiphidiumXvariatusXandersiXmilleriXevelynaeXmaculatus_JpWildXclemenciae_GXmonticolusXsignumXmayaeXhelleriiXalvareziPsjonesiiPriapellaXmeyeriXgordoniXcouchianusXxiphidiumXandersiXvariatusXevelynaeXmilleriXmaculatus_JpWildXclemenciae_GXmonticolusXmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiPsjonesiiPriapellaXnezahuacoyotlXcorteziXmalinche_CHIC2Xbirchmanni_GARCXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiXclemenciae_GXmonticolusXxiphidiumXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXandersiXmaculatus_JpWildPsjonesiiPriapella

Setting a fixed_order to the tips of the tree makes it easier to see discordance among multiple trees. Here I first infer a consensus tree and then use the tip order of the consensus tree to order and plot the first few trees from the treelist.

[18]:
# get majority-rule consensus tree
consfish = fish.get_consensus_tree()

# draw tree grid and use consensus tree order as a fixed_order of tips
cfish.draw(
    nrows=2,
    ncols=3,
    height=600,
    width=600,
    fixed_order=True,
    edge_type='c',
    shared_axes=True,
);
Xmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXhelleriiXsignumXmonticolusXclemenciae_GXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXxiphidiumXandersiXmaculatus_JpWildPsjonesiiPriapellaXnezahuacoyotlXcorteziXbirchmanni_GARCXmalinche_CHIC2XmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmeyeriXgordoniXcouchianusXxiphidiumXvariatusXandersiXmilleriXevelynaeXmaculatus_JpWildXclemenciae_GXmonticolusXsignumXmayaeXhelleriiXalvareziPsjonesiiPriapellaXmeyeriXgordoniXcouchianusXxiphidiumXandersiXvariatusXevelynaeXmilleriXmaculatus_JpWildXclemenciae_GXmonticolusXmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiPsjonesiiPriapellaXnezahuacoyotlXcorteziXmalinche_CHIC2Xbirchmanni_GARCXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiXclemenciae_GXmonticolusXxiphidiumXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXandersiXmaculatus_JpWildPsjonesiiPriapellaXnigrensisXmultilineatusXpygmaeusXcontinensXcorteziXmalinche_CHIC2Xbirchmanni_GARCXnezahuacoyotlXmontezumaeXxiphidiumXvariatusXevelynaeXmeyeriXgordoniXcouchianusXandersiXmilleriXmaculatus_JpWildXsignumXmayaeXhelleriiXalvareziXclemenciae_GXmonticolusPsjonesiiPriapellaXnigrensisXmultilineatusXpygmaeusXcontinensXmontezumaeXnezahuacoyotlXcorteziXmalinche_CHIC2Xbirchmanni_GARCXmeyeriXgordoniXxiphidiumXcouchianusXvariatusXevelynaeXmilleriXandersiXmayaeXalvareziXhelleriiXsignumXmonticolusXclemenciae_GXmaculatus_JpWildPsjonesiiPriapella

Fixed tip order

When drawing CloudTrees the order of names at the tips is always fixed across individual ToyTrees, this is required in order to see disagreement among topologies. For example, we can fix the order to match the first tree in the treelist by using fixed_order=True. But more often you will want to set it to some specific order, like we did above by using a consensus tree order, fixed_order=[list-of-names].

If you want to change the names of the tips of the CloudTree, or style them at the time of plotting, this can be done by providing a dictionary object mapping the old names to the new ones. In the example below I create a dictionary argument to tip_labels that uses the labels from the consensus tree (cfish) as keys, and modifies those strings as the values of the dictionary.

[19]:
# draw a cloud tree which enforces a fixed tip order
fish.draw_cloud_tree(
    fixed_order=consfish.get_tip_labels(),
    tip_labels_style={"font-size": "11px"},
    tip_labels=[
        '{}. {}'.format(i[0], i[1:])
        for i in fish.treelist[0].get_tip_labels()
    ],
);
X. malinche_CHIC2X. birchmanni_GARCX. corteziX. nezahuacoyotlX. montezumaeX. nigrensisX. multilineatusX. pygmaeusX. continensX. mayaeX. alvareziX. helleriiX. signumX. monticolusX. clemenciae_GX. meyeriX. gordoniX. couchianusX. variatusX. evelynaeX. milleriX. xiphidiumX. andersiX. maculatus_JpWildP. sjonesiiP. riapella

Custom tip order

If the fixed_order argument is provided as a list of names then tips of the tree will be ordered according to the list. Take note: the structure of the relationships in the tree (e.g., the newick representation) does not change with fixed_order, this is simply changing the order that tips are presented when plotting. For example, the tip order below was used in the published paper by Cui et al. since it shows the geographic distributions of clades nicely ordered from north to south. When entering names as a list the order of names is plotted from bottom (x axis=0) to the top location on a right-facing tree.

[20]:
customorder = [
    "Priapella",
    "Psjonesii",
    "Xmayae",
    "Xalvarezi",
    "Xhellerii",
    "Xsignum",
    "Xmonticolus",
    "Xclemenciae_G",
    "Xbirchmanni_GARC",
    "Xmalinche_CHIC2",
    "Xcortezi",
    "Xnezahuacoyotl",
    "Xmontezumae",
    "Xcontinens",
    "Xpygmaeus",
    "Xmultilineatus",
    "Xnigrensis",
    "Xgordoni",
    "Xmeyeri",
    "Xcouchianus",
    "Xxiphidium",
    "Xvariatus",
    "Xevelynae",
    "Xmilleri",
    "Xandersi",
    "Xmaculatus_JpWild",
]
[22]:
# set fixed tip order
fish.draw(
    height=300,
    width=600,
    fixed_order=customorder,
    edge_type='c',
);
Xmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXhelleriiXsignumXmonticolusXclemenciae_GXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXxiphidiumXandersiXmaculatus_JpWildPsjonesiiPriapellaXnezahuacoyotlXcorteziXbirchmanni_GARCXmalinche_CHIC2XmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmeyeriXgordoniXcouchianusXxiphidiumXvariatusXandersiXmilleriXevelynaeXmaculatus_JpWildXclemenciae_GXmonticolusXsignumXmayaeXhelleriiXalvareziPsjonesiiPriapellaXmeyeriXgordoniXcouchianusXxiphidiumXandersiXvariatusXevelynaeXmilleriXmaculatus_JpWildXclemenciae_GXmonticolusXmalinche_CHIC2Xbirchmanni_GARCXcorteziXnezahuacoyotlXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiPsjonesiiPriapellaXnezahuacoyotlXcorteziXmalinche_CHIC2Xbirchmanni_GARCXmontezumaeXnigrensisXmultilineatusXpygmaeusXcontinensXmayaeXalvareziXsignumXhelleriiXclemenciae_GXmonticolusXxiphidiumXmeyeriXgordoniXcouchianusXvariatusXevelynaeXmilleriXandersiXmaculatus_JpWildPsjonesiiPriapella

CloudTree Styling

CloudTree drawings can use the same style arguments as ToyTrees drawings. For example, the edge_style dictionary can be used to modify the edge colors (stroke) and opacity. Here I fancy up the tip names a bit as well and add some additional points at the tips of the tree using the toyplot scatterplot function. The finished figure looks quite similar to the published figure in Cui et al. (2013).

[26]:
# draw the cloudtree
canvas, axes, mark = fish.draw_cloud_tree(
    height=450,
    edge_style={
        'stroke': toyplot.color.brewer.palette("BlueGreen")[4],
        'stroke-opacity': 0.05,
    },
    fixed_order=customorder,
    tip_labels=[
        "{}. {}".format(i[0], i[1:]) for i in fish.treelist[0].get_tip_labels()
    ]
);

# add colored nodes at the tips (x-axis=0) (y-axis=0-ntips)
xlocs = np.zeros(fish.ntips)
ylocs = np.arange(fish.ntips)
colors = np.concatenate([
    [toytree.colors[2]] * 2,
    [toytree.colors[1]] * 6,
    [toytree.colors[5]] * 9,
    [toytree.colors[0]] * 9,
])
axes.scatterplot(
    xlocs + 0.05,
    ylocs,
    color=colors,
    mstyle={"stroke": "#262626", "stroke-width": 0.75},
    size=6,
);
X. malinche_CHIC2X. birchmanni_GARCX. corteziX. nezahuacoyotlX. montezumaeX. nigrensisX. multilineatusX. pygmaeusX. continensX. mayaeX. alvareziX. helleriiX. signumX. monticolusX. clemenciae_GX. meyeriX. gordoniX. couchianusX. variatusX. evelynaeX. milleriX. xiphidiumX. andersiX. maculatus_JpWildP. sjonesiiP. riapella