Toytree: a minimalist Python tree plotting library

What is Toytree?

Toytree is a Python tree plotting library that uses the modern and minimalist graphics library Toyplot to generate high quality figures that can be displayed directly within jupyter-notebooks or exported in a variety of formats. Find the full documentation at http://toytree.readthedocs.io.

Installation

Toyplot can be easily installed using the conda software package manager with the command below.

In [1]:
## conda install toytree -c eaton-lab

To get started let's import a few modules

Import toytree, toyplot, and the numerical library numpy for generating data. If you installed toytree with conda then all of these modules will be available.

In [2]:
import toytree
import toyplot
import numpy
In [3]:
toytree.__version__
Out[3]:
'0.0.10'

Loading/parsing trees

Toytree can parse a wide variety of tree formats in newick or extended newick format, where trees are represented as a set of nested parentheses with additional metadata linked to nodes.

In [3]:
## an example newick string with edge lengths and support values
newick = """
((apple:2,orange:4)100:2,(((tomato:2,eggplant:1)
100:2,pepper:3)90:1,tomatillo:2)100:1);
"""

## create a toytree object from the newick string
tre = toytree.tree(newick)
In [4]:
## A toytree object in memory
tre
Out[4]:
<toytree.tree.Toytree at 0x7f0869e54050>

Simple tree plotting

To draw a tree from a Toytree object simply call its .draw() function which will generate a Canvas and a set of axes on which the plot is embedded. The Canvas is represented in HTML which allows it to be displayed seemlessly in a jupyter-notebook, and to look crisp on the web, as in this notebook presentation.

In [5]:
tre.draw(width=200);
appleorangetomatillopeppertomatoeggplant

Interactive features

A fun advantage to generating figures in HTML is that you can easily add interactive features to your plots. In the example below I use the magic argument node_labels=True to diplay markers on nodes with an interactive hover feature. Hover your cursor over a node to see all of the features associated with it. Node features are parsed from the newick string, or can be added later.

In [6]:
tre.draw(width=200, node_labels=True);
appleorangetomatillopeppertomatoeggplantidx: 0 name: 0 dist: 0 support: 1idx: 1 name: 1 dist: 2 support: 100idx: 2 name: 2 dist: 1 support: 100idx: 3 name: 3 dist: 1 support: 90idx: 4 name: 4 dist: 2 support: 100idx: 5 name: apple dist: 2 support: 1idx: 6 name: orange dist: 4 support: 1idx: 7 name: tomatillo dist: 2 support: 1idx: 8 name: pepper dist: 3 support: 1idx: 9 name: tomato dist: 2 support: 1idx: 10 name: eggplant dist: 1 support: 1

Displaying node features

Besides the magic command node_values=True you can also set specific node features to be displayed on the tree. Or you can access node values to create custom lists to use as arguments to modify nodes colors or sizes. This is all controlled through arguments to the draw() function.

In [7]:
## get list of support values in node-order
supports = tre.get_node_values("support", show_root=False, show_tips=False)

## get list of node colors in node-order 
colors = [tre.colors[0] if i==100 else tre.colors[1] for i in supports]

## draw with node supports and some styling
tre.draw(
    width=300,
    node_labels=supports,
    node_color=colors,
    node_size=20,
    node_style={"stroke": "none"}
    );
appleorangetomatillopeppertomatoeggplant10010090100

More on features and parsing trees

Toyplot uses a stripped-down version of the TreeNode object from the ete3 library to represent Tree objects in memory as a hierarchical structure that is easy to traverse and manipulate. This makes it easy to store and access meta-data on a tree in a programmatic way. See the ete documentation for many helpful tips on editing trees. In this example we add a new feature to each node and write the new tree string in extended newick format.

In [11]:
## a simple tree
tre = toytree.tree("(((a,b),c),d);")

## add a new feature to every node
for node in tre.tree.traverse():
    node.add_feature("new_feature", 100)
    
## write the tree string in NHX format with features
nhx = tre.tree.write(features=["new_feature", "dist", "support"], format=0)

## print the newick string
nhx
Out[11]:
'(d:1[&&NHX:new_feature=100:dist=1.0:support=1.0],(c:1[&&NHX:new_feature=100:dist=1.0:support=1.0],(a:1[&&NHX:new_feature=100:dist=1.0:support=1.0],b:1[&&NHX:new_feature=100:dist=1.0:support=1.0])1:1[&&NHX:new_feature=100:dist=1.0:support=1.0])1:1[&&NHX:new_feature=100:dist=1.0:support=1.0]);'

Viewing large trees

Viewing large trees is a bit easier with toytree than with alternative graphics libraries because the images can be easily viewed in a browser. Here we read in a large tree and display it. Scroll down to view the entire tree.

In [12]:
## load a newick string from a file path
newick = "/home/deren/Dropbox/Viburnum_t65_min4"
tre = toytree.tree(newick)

## draw it
tre.draw(
    width=500,
    height=900,
    node_labels=tre.get_node_values("support"),
    );
clemensiae_DRY6_PWS_2135urceolatum_MJD_Japan_8taiwanianum_TW1_KFC_1952nervosum_C4_PWS_2298lantanoides_D15_Beartown_2sympodiale_D18_KFC_1932furcatum_combinedpunctatum_D19_PWS_2097cassinoides_ELS2lentago_ELS85rufidulum_ELS25prunifolium_ELS57lantana_combinedrhytidophyllumveitchiimacrocephalum_D2_WC_284carlesii_D1_BP_001bitchuense_combinedamplificatum_D3_SAN_156003lutescens_D35_PWS_2077plicatum_C1_MJDJP_12hanceanum_D4_PWS_2195sieboldii_AA_616_6Bodoratissimum_combinedhenryi_D22_WC_272erubescens_RCW36suspensum_C5_MJD_111711farreri_RCW21grandiflorum_ERAD11_Wendyfoetens_ERAD10tinus_D33_WC_277davidii_D32_WC_269propinquum_DRY4_WC_276cinnamomifolium_PWS2105Xrecognitum_AA_1471_83Bdentatum_ELS4sulcatum_D9_MEX_003acutifolium_DRY3_MEX_006triphyllum_D13_PWS_1783jamesonii_D12_PWS_1636opulus_D6_WC_250sargentii_RCW19coriaceum_combinedcylindricum_DRY1_WC_268sambucina_D20_PWS_2100beccarii_combinedXvernicosum_D21_PWS_2123glaberrimum_D34_PWS_2323orientale_DRY2_MJD_GEORGIAacerfolium_ELS88foetidum_D24_KFC_1942betulifoliummuhalla_D28_WC_274integrifolium_D25_KFC_1946japonicum_D26_WC_273wrightii_D31_MJDJP_1parvifolium_D29_KFC_1953dilatatum_ELS45erosum_D23_MJDJP_4tashiori_D30_TET_YAHformosanum_C7_JH_2007luzonicum_D27_9M_2005setigerumsempervirens_combinedanamensis_C6_PWS_2094100100100100100100100100100100831001001001001001001001001001001001001009810010010010010010010010010010010010010010010010010010010010010010010010010010010010010010010010010093100100100100

Displaying cloudtrees

It can be informative to visualize a distribution of trees, such as a posterior distribution of species trees, and a cloud tree diagram which is easy to do with toyplot since opacity is an easy variable to modify. Here we create a MultiTree object which holds a list of Toytrees in memory, and call the draw_cloudtree() function.

In [19]:
## a file with trees sampled from a posterior dist.
mcmcfile = "/home/deren/Documents/ipyrad/tests/analysis-bpp/copy-d10-r0.mcmc.txt"

## use 'slice' operations to subsample every 10th tree from first 5000
mtrees = toytree.multitree(mcmcfile, treeslice=(0, 5000, 10))

## plot a cloud tree
mtrees.draw_cloudtree(
    width=350,
    height=300,
    use_edge_lengths=True,
    orient='right',
    edge_style={
        "opacity": 0.025, 
        "stroke": mtrees.colors[0]},
    tip_labels_style={
        "-toyplot-anchor-shift": "10px"},
    );
tharexliprckcupcys

Combining with Toytree with Toyplot

The toyplot library has many plotting features that can easily be combined with toytree plots.

In [44]:
## create a canvas and axes
canvas = toyplot.Canvas(width=400, height=550)
axes = canvas.cartesian()

## create some data
data = numpy.random.randint(5, 15, len(tre))

## add tree to the axes with the axes argument 
_, axes = tre.draw(
    axes=axes,
    tip_labels=False,
    );

## add barplot to the same axes
axes.bars(data[::-1], 
          along='y', 
          baseline=[1]*len(tre),
          title=["name={}; val={}".format(i, j) for i, j in \
                 zip(tre.get_tip_labels(), data[::-1])]
          )

## hide axes
axes.show=False
name=clemensiae_DRY6_PWS_2135; val=14name=urceolatum_MJD_Japan_8; val=11name=taiwanianum_TW1_KFC_1952; val=6name=nervosum_C4_PWS_2298; val=14name=lantanoides_D15_Beartown_2; val=6name=sympodiale_D18_KFC_1932; val=7name=furcatum_combined; val=8name=punctatum_D19_PWS_2097; val=6name=cassinoides_ELS2; val=11name=lentago_ELS85; val=10name=rufidulum_ELS25; val=13name=prunifolium_ELS57; val=8name=lantana_combined; val=12name=rhytidophyllum; val=14name=veitchii; val=9name=macrocephalum_D2_WC_284; val=5name=carlesii_D1_BP_001; val=14name=bitchuense_combined; val=9name=amplificatum_D3_SAN_156003; val=8name=lutescens_D35_PWS_2077; val=13name=plicatum_C1_MJDJP_12; val=13name=hanceanum_D4_PWS_2195; val=5name=sieboldii_AA_616_6B; val=10name=odoratissimum_combined; val=5name=henryi_D22_WC_272; val=7name=erubescens_RCW36; val=6name=suspensum_C5_MJD_111711; val=9name=farreri_RCW21; val=8name=grandiflorum_ERAD11_Wendy; val=14name=foetens_ERAD10; val=6name=tinus_D33_WC_277; val=10name=davidii_D32_WC_269; val=11name=propinquum_DRY4_WC_276; val=8name=cinnamomifolium_PWS2105X; val=13name=recognitum_AA_1471_83B; val=9name=dentatum_ELS4; val=5name=sulcatum_D9_MEX_003; val=14name=acutifolium_DRY3_MEX_006; val=13name=triphyllum_D13_PWS_1783; val=12name=jamesonii_D12_PWS_1636; val=10name=opulus_D6_WC_250; val=7name=sargentii_RCW19; val=10name=coriaceum_combined; val=7name=cylindricum_DRY1_WC_268; val=11name=sambucina_D20_PWS_2100; val=6name=beccarii_combinedX; val=13name=vernicosum_D21_PWS_2123; val=7name=glaberrimum_D34_PWS_2323; val=13name=orientale_DRY2_MJD_GEORGIA; val=9name=acerfolium_ELS88; val=14name=foetidum_D24_KFC_1942; val=12name=betulifolium; val=8name=muhalla_D28_WC_274; val=14name=integrifolium_D25_KFC_1946; val=5name=japonicum_D26_WC_273; val=13name=wrightii_D31_MJDJP_1; val=12name=parvifolium_D29_KFC_1953; val=8name=dilatatum_ELS45; val=11name=erosum_D23_MJDJP_4; val=6name=tashiori_D30_TET_YAH; val=8name=formosanum_C7_JH_2007; val=12name=luzonicum_D27_9M_2005; val=9name=setigerum; val=10name=sempervirens_combined; val=10name=anamensis_C6_PWS_2094; val=8