Skip to content

Commit d57fa97

Browse files
authored
Merge pull request #27815 from anjabeck/side-option-violinplot
Add side option to violinplot
2 parents 76eaa96 + 0adab7b commit d57fa97

File tree

7 files changed

+88
-20
lines changed

7 files changed

+88
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add option to plot only one half of violin plot
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Setting the parameter *side* to 'low' or 'high' allows to only plot one half of the violin plot.

galleries/examples/statistics/violinplot.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -28,55 +28,73 @@
2828
pos = [1, 2, 4, 5, 7, 8]
2929
data = [np.random.normal(0, std, size=100) for std in pos]
3030

31-
fig, axs = plt.subplots(nrows=2, ncols=5, figsize=(10, 6))
31+
fig, axs = plt.subplots(nrows=2, ncols=6, figsize=(10, 4))
3232

3333
axs[0, 0].violinplot(data, pos, points=20, widths=0.3,
3434
showmeans=True, showextrema=True, showmedians=True)
35-
axs[0, 0].set_title('Custom violinplot 1', fontsize=fs)
35+
axs[0, 0].set_title('Custom violin 1', fontsize=fs)
3636

3737
axs[0, 1].violinplot(data, pos, points=40, widths=0.5,
3838
showmeans=True, showextrema=True, showmedians=True,
3939
bw_method='silverman')
40-
axs[0, 1].set_title('Custom violinplot 2', fontsize=fs)
40+
axs[0, 1].set_title('Custom violin 2', fontsize=fs)
4141

4242
axs[0, 2].violinplot(data, pos, points=60, widths=0.7, showmeans=True,
4343
showextrema=True, showmedians=True, bw_method=0.5)
44-
axs[0, 2].set_title('Custom violinplot 3', fontsize=fs)
44+
axs[0, 2].set_title('Custom violin 3', fontsize=fs)
4545

4646
axs[0, 3].violinplot(data, pos, points=60, widths=0.7, showmeans=True,
4747
showextrema=True, showmedians=True, bw_method=0.5,
4848
quantiles=[[0.1], [], [], [0.175, 0.954], [0.75], [0.25]])
49-
axs[0, 3].set_title('Custom violinplot 4', fontsize=fs)
49+
axs[0, 3].set_title('Custom violin 4', fontsize=fs)
5050

5151
axs[0, 4].violinplot(data[-1:], pos[-1:], points=60, widths=0.7,
5252
showmeans=True, showextrema=True, showmedians=True,
5353
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5)
54-
axs[0, 4].set_title('Custom violinplot 5', fontsize=fs)
54+
axs[0, 4].set_title('Custom violin 5', fontsize=fs)
55+
56+
axs[0, 5].violinplot(data[-1:], pos[-1:], points=60, widths=0.7,
57+
showmeans=True, showextrema=True, showmedians=True,
58+
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='low')
59+
60+
axs[0, 5].violinplot(data[-1:], pos[-1:], points=60, widths=0.7,
61+
showmeans=True, showextrema=True, showmedians=True,
62+
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
63+
axs[0, 5].set_title('Custom violin 6', fontsize=fs)
5564

5665
axs[1, 0].violinplot(data, pos, points=80, vert=False, widths=0.7,
5766
showmeans=True, showextrema=True, showmedians=True)
58-
axs[1, 0].set_title('Custom violinplot 6', fontsize=fs)
67+
axs[1, 0].set_title('Custom violin 7', fontsize=fs)
5968

6069
axs[1, 1].violinplot(data, pos, points=100, vert=False, widths=0.9,
6170
showmeans=True, showextrema=True, showmedians=True,
6271
bw_method='silverman')
63-
axs[1, 1].set_title('Custom violinplot 7', fontsize=fs)
72+
axs[1, 1].set_title('Custom violin 8', fontsize=fs)
6473

6574
axs[1, 2].violinplot(data, pos, points=200, vert=False, widths=1.1,
6675
showmeans=True, showextrema=True, showmedians=True,
6776
bw_method=0.5)
68-
axs[1, 2].set_title('Custom violinplot 8', fontsize=fs)
77+
axs[1, 2].set_title('Custom violin 9', fontsize=fs)
6978

7079
axs[1, 3].violinplot(data, pos, points=200, vert=False, widths=1.1,
7180
showmeans=True, showextrema=True, showmedians=True,
7281
quantiles=[[0.1], [], [], [0.175, 0.954], [0.75], [0.25]],
7382
bw_method=0.5)
74-
axs[1, 3].set_title('Custom violinplot 9', fontsize=fs)
83+
axs[1, 3].set_title('Custom violin 10', fontsize=fs)
7584

7685
axs[1, 4].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
7786
showmeans=True, showextrema=True, showmedians=True,
7887
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5)
79-
axs[1, 4].set_title('Custom violinplot 10', fontsize=fs)
88+
axs[1, 4].set_title('Custom violin 11', fontsize=fs)
89+
90+
axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
91+
showmeans=True, showextrema=True, showmedians=True,
92+
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='low')
93+
94+
axs[1, 5].violinplot(data[-1:], pos[-1:], points=200, vert=False, widths=1.1,
95+
showmeans=True, showextrema=True, showmedians=True,
96+
quantiles=[0.05, 0.1, 0.8, 0.9], bw_method=0.5, side='high')
97+
axs[1, 5].set_title('Custom violin 12', fontsize=fs)
8098

8199

82100
for ax in axs.flat:

lib/matplotlib/axes/_axes.py

+36-9
Original file line numberDiff line numberDiff line change
@@ -8205,7 +8205,7 @@ def matshow(self, Z, **kwargs):
82058205
@_preprocess_data(replace_names=["dataset"])
82068206
def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
82078207
showmeans=False, showextrema=True, showmedians=False,
8208-
quantiles=None, points=100, bw_method=None):
8208+
quantiles=None, points=100, bw_method=None, side='both'):
82098209
"""
82108210
Make a violin plot.
82118211
@@ -8256,6 +8256,10 @@ def violinplot(self, dataset, positions=None, vert=True, widths=0.5,
82568256
callable, it should take a `matplotlib.mlab.GaussianKDE` instance as
82578257
its only parameter and return a float.
82588258
8259+
side : {'both', 'low', 'high'}, default: 'both'
8260+
'both' plots standard violins. 'low'/'high' only
8261+
plots the side below/above the positions value.
8262+
82598263
data : indexable object, optional
82608264
DATA_PARAMETER_PLACEHOLDER
82618265
@@ -8307,10 +8311,10 @@ def _kde_method(X, coords):
83078311
quantiles=quantiles)
83088312
return self.violin(vpstats, positions=positions, vert=vert,
83098313
widths=widths, showmeans=showmeans,
8310-
showextrema=showextrema, showmedians=showmedians)
8314+
showextrema=showextrema, showmedians=showmedians, side=side)
83118315

83128316
def violin(self, vpstats, positions=None, vert=True, widths=0.5,
8313-
showmeans=False, showextrema=True, showmedians=False):
8317+
showmeans=False, showextrema=True, showmedians=False, side='both'):
83148318
"""
83158319
Draw a violin plot from pre-computed statistics.
83168320
@@ -8366,6 +8370,10 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
83668370
showmedians : bool, default: False
83678371
Whether to show the median with a line.
83688372
8373+
side : {'both', 'low', 'high'}, default: 'both'
8374+
'both' plots standard violins. 'low'/'high' only
8375+
plots the side below/above the positions value.
8376+
83698377
Returns
83708378
-------
83718379
dict
@@ -8428,8 +8436,13 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
84288436
elif len(widths) != N:
84298437
raise ValueError(datashape_message.format("widths"))
84308438

8439+
# Validate side
8440+
_api.check_in_list(["both", "low", "high"], side=side)
8441+
84318442
# Calculate ranges for statistics lines (shape (2, N)).
8432-
line_ends = [[-0.25], [0.25]] * np.array(widths) + positions
8443+
line_ends = [[-0.25 if side in ['both', 'low'] else 0],
8444+
[0.25 if side in ['both', 'high'] else 0]] \
8445+
* np.array(widths) + positions
84338446

84348447
# Colors.
84358448
if mpl.rcParams['_internal.classic_mode']:
@@ -8441,20 +8454,34 @@ def violin(self, vpstats, positions=None, vert=True, widths=0.5,
84418454
# Check whether we are rendering vertically or horizontally
84428455
if vert:
84438456
fill = self.fill_betweenx
8444-
perp_lines = functools.partial(self.hlines, colors=linecolor)
8445-
par_lines = functools.partial(self.vlines, colors=linecolor)
8457+
if side in ['low', 'high']:
8458+
perp_lines = functools.partial(self.hlines, colors=linecolor,
8459+
capstyle='projecting')
8460+
par_lines = functools.partial(self.vlines, colors=linecolor,
8461+
capstyle='projecting')
8462+
else:
8463+
perp_lines = functools.partial(self.hlines, colors=linecolor)
8464+
par_lines = functools.partial(self.vlines, colors=linecolor)
84468465
else:
84478466
fill = self.fill_between
8448-
perp_lines = functools.partial(self.vlines, colors=linecolor)
8449-
par_lines = functools.partial(self.hlines, colors=linecolor)
8467+
if side in ['low', 'high']:
8468+
perp_lines = functools.partial(self.vlines, colors=linecolor,
8469+
capstyle='projecting')
8470+
par_lines = functools.partial(self.hlines, colors=linecolor,
8471+
capstyle='projecting')
8472+
else:
8473+
perp_lines = functools.partial(self.vlines, colors=linecolor)
8474+
par_lines = functools.partial(self.hlines, colors=linecolor)
84508475

84518476
# Render violins
84528477
bodies = []
84538478
for stats, pos, width in zip(vpstats, positions, widths):
84548479
# The 0.5 factor reflects the fact that we plot from v-p to v+p.
84558480
vals = np.array(stats['vals'])
84568481
vals = 0.5 * width * vals / vals.max()
8457-
bodies += [fill(stats['coords'], -vals + pos, vals + pos,
8482+
bodies += [fill(stats['coords'],
8483+
-vals + pos if side in ['both', 'low'] else pos,
8484+
vals + pos if side in ['both', 'high'] else pos,
84588485
facecolor=fillcolor, alpha=0.3)]
84598486
means.append(stats['mean'])
84608487
mins.append(stats['min'])

lib/matplotlib/axes/_axes.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,7 @@ class Axes(_AxesBase):
743743
| float
744744
| Callable[[GaussianKDE], float]
745745
| None = ...,
746+
side: Literal["both", "low", "high"] = ...,
746747
*,
747748
data=...,
748749
) -> dict[str, Collection]: ...
@@ -755,6 +756,7 @@ class Axes(_AxesBase):
755756
showmeans: bool = ...,
756757
showextrema: bool = ...,
757758
showmedians: bool = ...,
759+
side: Literal["both", "low", "high"] = ...,
758760
) -> dict[str, Collection]: ...
759761

760762
table = mtable.table

lib/matplotlib/pyplot.py

+2
Original file line numberDiff line numberDiff line change
@@ -4075,6 +4075,7 @@ def violinplot(
40754075
| float
40764076
| Callable[[GaussianKDE], float]
40774077
| None = None,
4078+
side: Literal["both", "low", "high"] = "both",
40784079
*,
40794080
data=None,
40804081
) -> dict[str, Collection]:
@@ -4089,6 +4090,7 @@ def violinplot(
40894090
quantiles=quantiles,
40904091
points=points,
40914092
bw_method=bw_method,
4093+
side=side,
40924094
**({"data": data} if data is not None else {}),
40934095
)
40944096

lib/matplotlib/tests/test_axes.py

+15
Original file line numberDiff line numberDiff line change
@@ -3794,6 +3794,21 @@ def test_horiz_violinplot_custompoints_200():
37943794
showextrema=False, showmedians=False, points=200)
37953795

37963796

3797+
@image_comparison(['violinplot_sides.png'], remove_text=True, style='mpl20')
3798+
def test_violinplot_sides():
3799+
ax = plt.axes()
3800+
np.random.seed(19680801)
3801+
data = [np.random.normal(size=100)]
3802+
# Check horizontal violinplot
3803+
for pos, side in zip([0, -0.5, 0.5], ['both', 'low', 'high']):
3804+
ax.violinplot(data, positions=[pos], vert=False, showmeans=False,
3805+
showextrema=True, showmedians=True, side=side)
3806+
# Check vertical violinplot
3807+
for pos, side in zip([4, 3.5, 4.5], ['both', 'low', 'high']):
3808+
ax.violinplot(data, positions=[pos], vert=True, showmeans=False,
3809+
showextrema=True, showmedians=True, side=side)
3810+
3811+
37973812
def test_violinplot_bad_positions():
37983813
ax = plt.axes()
37993814
# First 9 digits of frac(sqrt(47))

0 commit comments

Comments
 (0)