scatterplot and combined polar histogram in matplotlib

Viewed 605

I am attempting to produce a plot like this which combines a cartesian scatter plot and a polar histogram. (Radial lines optional) enter image description here A similar solution (by Nicolas Legrand) exists for looking at differences in x and y (code here), but we need to look at ratios (i.e. x/y). enter image description here

More specifically, this is useful when we want to look at the relative risk measure which is the ratio of two probabilities.

The scatter plot on it's own is obviously not a problem, but the polar histogram is more advanced.

The most promising lead I have found is this central example from the matplotlib gallery here enter image description here

I have attempted to do this, but have run up against the limits of my matplotlib skills. Any efforts moving towards this goal would be great.

3 Answers

I'm sure that others will have better suggestions, but one method that gets something like you want (without the need for extra axes artists) is to use a polar projection with a scatter and bar chart together. Something like

import matplotlib.pyplot as plt
import numpy as np

x = np.random.uniform(size=100)
y = np.random.uniform(size=100)

r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)

h, b = np.histogram(phi, bins=np.linspace(0, np.pi/2, 21), density=True)
colors = plt.cm.Spectral(h / h.max())

ax = plt.subplot(111, projection='polar')
ax.scatter(phi, r, marker='.')
ax.bar(b[:-1], h, width=b[1:] - b[:-1], 
       align='edge', bottom=np.max(r) + 0.2,  color=colors)
# Cut off at 90 degrees
ax.set_thetamax(90)
# Set the r grid to cover the scatter plot
ax.set_rgrids([0, 0.5, 1])
# Let's put a line at 1 assuming we want a ratio of some sort
ax.set_thetagrids([45], [1])

which will give enter image description here

It is missing axes labels and some beautification, but it might be a place to start. I hope it is helpful.

You can use two axes on top of each other:

import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.8,.8], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")

ax2.set_rorigin(-1)
ax2.set_thetamax(90)
plt.show()

enter image description here

Ok. Thanks to the answer from Nicolas, and the answer from tomjn I have a working solution :)

import numpy as np
import matplotlib.pyplot as plt

# Scatter data
n = 50
x = 0.3 + np.random.randn(n)*0.1
y = 0.4 + np.random.randn(n)*0.02

def radial_corner_plot(x, y, n_hist_bins=51):
    """Scatter plot with radial histogram of x/y ratios"""

    # Axis setup
    fig = plt.figure(figsize=(6,6))
    ax1 = fig.add_axes([0.1,0.1,.6,.6], label="cartesian")
    ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
    ax2.set_rorigin(-20)
    ax2.set_thetamax(90)

    # define useful constant
    offset_in_radians = np.pi/4

    def rotate_hist_axis(ax):
        """rotate so that 0 degrees is pointing up and right"""
        ax.set_theta_offset(offset_in_radians)
        ax.set_thetamin(-45)
        ax.set_thetamax(45)
        return ax

    # Convert scatter data to histogram data
    r = np.sqrt(x**2 + y**2)
    phi = np.arctan2(y, x)
    h, b = np.histogram(phi, 
                        bins=np.linspace(0, np.pi/2, n_hist_bins),
                        density=True)

    # SCATTER PLOT -------------------------------------------------------
    ax1.scatter(x,y)

    ax1.set(xlim=[0, 1], ylim=[0, 1], xlabel="x", ylabel="y")
    ax1.spines['right'].set_visible(False)
    ax1.spines['top'].set_visible(False)


    # HISTOGRAM ----------------------------------------------------------
    ax2 = rotate_hist_axis(ax2)
    # rotation of axis requires rotation in bin positions
    b = b - offset_in_radians

    # plot the histogram
    bars = ax2.bar(b[:-1], h, width=b[1:] - b[:-1], align='edge')

    def update_hist_ticks(ax, desired_ratios):
        """Update tick positions and corresponding tick labels"""
        x = np.ones(len(desired_ratios))
        y = 1/desired_ratios
        phi = np.arctan2(y,x) - offset_in_radians
        # define ticklabels
        xticklabels = [str(round(float(label), 2)) for label in desired_ratios]
        # apply updates
        ax2.set(xticks=phi, xticklabels=xticklabels)
        return ax

    ax2 = update_hist_ticks(ax2, np.array([1/8, 1/4, 1/2, 1, 2, 4, 8]))

    # just have radial grid lines
    ax2.grid(which="major", axis="y")

    # remove bin count labels
    ax2.set_yticks([])

    return (fig, [ax1, ax2])

fig, ax = radial_corner_plot(x, y)

Thanks for the pointers!

enter image description here

Related