Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to plot scipy.hierarchy.dendrogram using polar coordinates?

I'm trying to adapt the following resources to this question:

Python conversion between coordinates

https://matplotlib.org/gallery/pie_and_polar_charts/polar_scatter.html

I can't seem to get the coordinates to transfer the dendrogram shape over to polar coordinates.

Does anyone know how to do this? I know there is an implementation in networkx but that requires building a graph and then using pygraphviz backend to get the positions.

Is there a way to convert dendrogram cartesian coordinates to polar coordinates with matplotlib and numpy?

import requests
from ast import literal_eval
import matplotlib.pyplot as plt
import numpy as np 

def read_url(url):
    r = requests.get(url)
    return r.text

def cartesian_to_polar(x, y):
    rho = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)
    return(rho, phi)

def plot_dendrogram(icoord,dcoord,figsize, polar=False):
    if polar:
        icoord, dcoord = cartesian_to_polar(icoord, dcoord)
    with plt.style.context("seaborn-white"):
        fig = plt.figure(figsize=figsize)
        ax = fig.add_subplot(111, polar=polar)
        for xs, ys in zip(icoord, dcoord):
            ax.plot(xs,ys, color="black")
        ax.set_title(f"Polar= {polar}", fontsize=15)

# Load the dendrogram data
string_data = read_url("https://pastebin.com/raw/f953qgdr").replace("\r","").replace("\n","").replace("\u200b\u200b","")

# Convert it to a dictionary (a subset of the output from scipy.hierarchy.dendrogram)
dendrogram_data = literal_eval(string_data)
icoord = np.asarray(dendrogram_data["icoord"], dtype=float)
dcoord = np.asarray(dendrogram_data["dcoord"], dtype=float)

# Plot the cartesian version
plot_dendrogram(icoord,dcoord, figsize=(8,3), polar=False)

# Plot the polar version
plot_dendrogram(icoord,dcoord, figsize=(5,5), polar=True)

enter image description here

I just tried this and it's closer but still not correct:

import matplotlib.transforms as mtransforms
with plt.style.context("seaborn-white"):
    fig, ax = plt.subplots(figsize=(5,5))
    for xs, ys in zip(icoord, dcoord):
        ax.plot(xs,ys, color="black",transform=trans_offset)

    ax_polar = plt.subplot(111, projection='polar')
    trans_offset = mtransforms.offset_copy(ax_polar.transData, fig=fig)
    for xs, ys in zip(icoord, dcoord):
        ax_polar.plot(xs,ys, color="black",transform=trans_offset)

enter image description here

like image 773
O.rka Avatar asked Aug 20 '18 18:08

O.rka


People also ask

What is Scipy cluster hierarchy?

cluster. hierarchy ) These functions cut hierarchical clusterings into flat clusterings or find the roots of the forest formed by a cut by providing the flat cluster ids of each observation.


1 Answers

You can make the "root" of the tree start in the middle and have the leaves outside. You also have to add more points to the "bar" part for it to look nice and round.

We note that each element of icoord and dcoord (I will call this seg) has four points:

seg[1]        seg[2]
+-------------+
|             |
+ seg[0]      + seg[3]

The vertical bars are fine as straight lines between the two points, but we need more points between seg[1] and seg[2] (the horizontal bar, which will need to become an arc).

This function will add more points in those positions and can be called on both xs and ys in the plotting function:

def smoothsegment(seg, Nsmooth=100):
    return np.concatenate([[seg[0]], np.linspace(seg[1], seg[2], Nsmooth), [seg[3]]])

Now we must modify the plotting function to calculate the radial coordinates. Some experimentation has led to the log formula I am using, based on the other answer which also uses log scale. I've left a gap open on the right for the radial labels and done a very rudimentary mapping of the "icoord" coordinates to the radial ones so that the labels correspond to the ones in the rectangular plot. I don't know exactly how to handle the radial dimension. The numbers are correct for the log, but we probably want to map them as well.

def plot_dendrogram(icoord,dcoord,figsize, polar=False):
    if polar:
        dcoord = -np.log(dcoord+1)
        # avoid a wedge over the radial labels
        gap = 0.1
        imax = icoord.max()
        imin = icoord.min()
        icoord = ((icoord - imin)/(imax - imin)*(1-gap) + gap/2)*2*numpy.pi
    with plt.style.context("seaborn-white"):
        fig = plt.figure(figsize=figsize)
        ax = fig.add_subplot(111, polar=polar)
        for xs, ys in zip(icoord, dcoord):
            if polar:
                xs = smoothsegment(xs)
                ys = smoothsegment(ys)
            ax.plot(xs,ys, color="black")
        ax.set_title(f"Polar= {polar}", fontsize=15)
        if polar:
            ax.spines['polar'].set_visible(False)
            ax.set_rlabel_position(0)
            Nxticks = 10
            xticks = np.linspace(gap/2, 1-gap/2, Nxticks)
            ax.set_xticks(xticks*np.pi*2)
            ax.set_xticklabels(np.round(np.linspace(imin, imax, Nxticks)).astype(int))

Which results in the following figure:

Radial dendrogram

like image 50
chthonicdaemon Avatar answered Sep 22 '22 09:09

chthonicdaemon