pythonmatplotlibbioinformaticsbiopythonphylogeny

Change which axis is used as radial/angular position with matplotlib polar=True (Creating circular phylogenetic tree with Bio.Phylo)


If I create a phylogenetic tree using Biopython Phylo module, it is oriented from left to right, e.g.:

import matplotlib.pyplot as plt
from Bio import Phylo
from io import StringIO

handle = StringIO("(((A,B),(C,D)),(E,F,G));")
tree = Phylo.read(handle, "newick")

fig, ax = plt.subplots(1, 1, figsize=(6, 6))
Phylo.draw(tree, axes=ax, do_show=False)
plt.savefig("./tree_rect.png")
plt.close(fig)

produces enter image description here

If I now try to convert it to a circular tree using the built-in polar transformation of matplotlib, I obtain:

fig, ax = plt.subplots(subplot_kw=dict(polar=True), figsize=(6, 6))
Phylo.draw(tree, axes=ax, do_show=False)
plt.savefig("./tree_circ.png")
plt.close(fig)

I obtain
enter image description here
that is matplotlib takes y-axis as the radial axis and x-axis as the angle. This is the opposite of what I need for obtaining a correct layout, which I can do, e.g., using ete3:

from ete3 import Tree, TreeStyle
t = Tree("(((A,B),(C,D)),(E,F,G));")
circular_style = TreeStyle()
circular_style.mode = "c"
circular_style.scale = 20
t.render("tree_ete3.png", w=183, units="mm", tree_style=circular_style)

enter image description here

(How) could this be done using Bio.Phylo? I suppose the possibilities are

but I don't know how to do either (and whether it can be done.)

Related question: Python: How to convert a phylogenetic tree into a circular format?


Solution

  • Here is an example swapping the "x" and "y" coordinates in a matplotlib polar plot so that a radial phylogenetic tree is correctly branched

    Below, I show how varying a scaling parameter in the code ('Scale'z) affects the resulting tree, and also provide for comparison the normal Bio.Phylo.draw(tree) version of the map.


    Further detailing work could be done to mimic plotting portions of the circumference of the polar circle (i.e., arcs) at branch points, as would typically be seen in a radial phylogenetic map. Also, as it is, the transformation performed here on the coordinates does not seem to do well at extending perfectly balanced within and throughout the circle.
    import matplotlib.pyplot as plt
    import numpy as np
    from Bio import Phylo
    from io import StringIO
    
    
    def transform_coordinates(
        clade, r=0, theta=0, delta_theta=np.pi / 32, coordinates=None
    ):
        if coordinates is None:
            coordinates = {}
    
        coordinates[clade] = (r, theta)
    
        if clade.is_terminal():
            theta += delta_theta
        else:
            for child in clade:
                coordinates, theta = transform_coordinates(
                    child, r + 1, theta, delta_theta, coordinates
                )
    
        return coordinates, theta
    
    
    def plot_tree(tree, coordinates, scale=1):
        fig, ax = plt.subplots(subplot_kw=dict(polar=True), figsize=(6, 6))
        for clade in tree.find_clades():
            if not clade.is_terminal():
                for child in clade:
                    x1, y1 = coordinates[clade]
                    x2, y2 = coordinates[child]
                    ax.plot([y1, y2], [x1, x2], "k-")
                    if child.is_terminal():
                        # print(child.name, x2, y2, np.cos(x2), np.cos(y2))
                        shift_y = 0.2 if np.cos(y2) < 0 else 0.05
                        ax.annotate(
                            child.name, xy=(y2 * 1.02, x2 + shift_y), color="red"
                        )
                    ax.grid("off")
                    ax.patch.set_visible(False)
                    ax.spines["polar"].set_visible(False)
                    ax.xaxis.grid(False)
                    ax.xaxis.set_visible(False)
                    ax.yaxis.grid(False)
                    ax.yaxis.set_visible(False)
        plt.title(f"Scale = {scale}\n")
        plt.show()
    
    
    handle = StringIO("((((A,B),(X,Y,Z),(H,I,J))),(((C,L),(D,K)),(E,F,G)));")
    tree = Phylo.read(handle, "newick")
    
    for z in range(12, 36, 4):
        coordinates, _ = transform_coordinates(tree.clade, delta_theta=np.pi / z)
        plot_tree(tree, coordinates, scale=z)
    

    Phylogenetic maps as matplotlib polar plots

    Note: The swapping of x & y coordinates within plot_tree: ax.plot([y1, y2], [x1, x2], "k-").