Description
Problem
Recently, I wanted to get split violin plots at defined x-positions (see image).
My problem is that while it's possible to specify the x-position in pyplot.violinplot()
, there is no option for split violins.
This is not true anymore since v0.13seaborn.violinplot()
on the other hand allows split violins but seems to enforce discrete x-positions with fixed intervals.
This example shows two data sets (black and red/blue) in four categories defined by
The code that produces the plot based on this answer on stackoverflow.
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(0)
cols = ["firebrick","dodgerblue"]
leg = {"patches": [], "labels": []}
# Loop over categories A and B
for A in range(0,2):
xpos = []
dLeft = []
dRight = []
for B in range(0,2):
# Create data for the left and right violins in the current category
mu = np.random.randint(10,100)
dLeft.append(np.random.poisson(mu,1000))
dRight.append(np.random.normal(mu+10,np.sqrt(mu+10),1000))
# Create x-positions for the violins
xpos.append(B+(A-0.5)*0.15)
# Create right violins (colors)
vRight = plt.violinplot(dRight,positions=xpos,widths=0.1,showextrema=False,showmedians=False)
for vr in vRight["bodies"]:
# Get the center
m = np.mean(vr.get_paths()[0].vertices[:, 0])
# Get the rightmost point
r = np.max(vr.get_paths()[0].vertices[:, 0])
# Only allow values right of the center
vr.get_paths()[0].vertices[:, 0] = np.clip(vr.get_paths()[0].vertices[:, 0], m, r)
# Set category A color
vr.set_color(cols[A])
vr.set_alpha(1.)
# Create left violins (black)
vLeft = plt.violinplot(dLeft,positions=xpos,widths=0.15,showextrema=False,showmedians=False)
for vl in vLeft["bodies"]:
# Get the center
m = np.mean(vl.get_paths()[0].vertices[:, 0])
# Get the leftmost point
l = np.min(vl.get_paths()[0].vertices[:, 0])
# Only allow values left of the center
vl.get_paths()[0].vertices[:, 0] = np.clip(vl.get_paths()[0].vertices[:, 0], l, m)
# Set color
vl.set_color("k")
vl.set_alpha(1.)
# Add legend entry
leg["patches"].append(vRight["bodies"][0])
leg["labels"].append(fr"$A={A}$")
plt.legend(leg["patches"],leg["labels"])
plt.xlabel(r"$B$")
plt.xticks([0,1],["0","1"])
plt.xlim(-0.5,1.5)
plt.tight_layout()
plt.savefig("example_violins.png")
Proposed solution
An easy-to-use solution would be to add an argument in the violinplot()
function through which the user can define which side to plot. Something like asymetric=None, "below", "above"
, where "below"
/"above"
only plots the side below/above the values given to positions
.
This would mean that calling
vRight = plt.violinplot(dRight,positions=xpos,widths=0.1,showextrema=False,showmedians=False,asymetric="above")
internally executes this loop
for vr in vRight["bodies"]:
# Get the center
m = np.mean(vr.get_paths()[0].vertices[:, 0])
# Get the rightmost point
r = np.max(vr.get_paths()[0].vertices[:, 0])
# Only allow values right of the center
vr.get_paths()[0].vertices[:, 0] = np.clip(vr.get_paths()[0].vertices[:, 0], m, r)