Skip to content

Commit a97de58

Browse files
author
Uwe Hubert
committed
Custom cap widths in box and whisker plots in bxp() and boxplot()
1 parent e9958e1 commit a97de58

File tree

8 files changed

+107
-12
lines changed

8 files changed

+107
-12
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Custom cap widths in box and whisker plots in bxp() and boxplot()
2+
-----------------------------------------------------------------
3+
4+
New bxp() and boxplot() parameter capwidths allows to control the
5+
widths of the caps in box and whisker plots.
6+
7+
.. plot::
8+
:include-source: true
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
x = np.linspace(-7, 7, 140)
13+
x = np.hstack([-25, x, 25])
14+
fig, ax = plt.subplots()
15+
ax.boxplot([x, x], notch=1, capwidths=[0.01, 0.2])
16+
plt.show()

examples/statistics/boxplot_demo.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,18 @@ def fake_bootstrapper(n):
231231
plt.setp(bp['fliers'], markersize=3.0)
232232
plt.show()
233233

234+
235+
###############################################################################
236+
# Here we customize the widths of the caps .
237+
238+
x = np.linspace(-7, 7, 140)
239+
x = np.hstack([-25, x, 25])
240+
fig, ax = plt.subplots()
241+
242+
ax.boxplot([x, x], notch=1, capwidths=[0.01, 0.2])
243+
244+
plt.show()
245+
234246
#############################################################################
235247
#
236248
# .. admonition:: References

lib/matplotlib/axes/_axes.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3520,7 +3520,8 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
35203520
showbox=None, showfliers=None, boxprops=None,
35213521
labels=None, flierprops=None, medianprops=None,
35223522
meanprops=None, capprops=None, whiskerprops=None,
3523-
manage_ticks=True, autorange=False, zorder=None):
3523+
manage_ticks=True, autorange=False, zorder=None,
3524+
capwidths=None):
35243525
"""
35253526
Draw a box and whisker plot.
35263527
@@ -3687,6 +3688,8 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
36873688
Show the arithmetic means.
36883689
capprops : dict, default: None
36893690
The style of the caps.
3691+
capwidths : float or array, default: None
3692+
The widths of the caps.
36903693
boxprops : dict, default: None
36913694
The style of the box.
36923695
whiskerprops : dict, default: None
@@ -3815,15 +3818,17 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
38153818
medianprops=medianprops, meanprops=meanprops,
38163819
meanline=meanline, showfliers=showfliers,
38173820
capprops=capprops, whiskerprops=whiskerprops,
3818-
manage_ticks=manage_ticks, zorder=zorder)
3821+
manage_ticks=manage_ticks, zorder=zorder,
3822+
capwidths=capwidths)
38193823
return artists
38203824

38213825
def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38223826
patch_artist=False, shownotches=False, showmeans=False,
38233827
showcaps=True, showbox=True, showfliers=True,
38243828
boxprops=None, whiskerprops=None, flierprops=None,
38253829
medianprops=None, capprops=None, meanprops=None,
3826-
meanline=False, manage_ticks=True, zorder=None):
3830+
meanline=False, manage_ticks=True, zorder=None,
3831+
capwidths=None):
38273832
"""
38283833
Drawing function for box and whisker plots.
38293834
@@ -3861,6 +3866,10 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38613866
The widths of the boxes. The default is
38623867
``clip(0.15*(distance between extreme positions), 0.15, 0.5)``.
38633868
3869+
capwidths : float or array-like, default: None
3870+
Either a scalar or a vector and sets the width of each cap.
3871+
The default is ``0.5*(with of the box)``, see *widths*.
3872+
38643873
vert : bool, default: True
38653874
If `True` (default), makes the boxes vertical.
38663875
If `False`, makes horizontal boxes.
@@ -3993,7 +4002,16 @@ def do_patch(xs, ys, **kwargs):
39934002
elif len(widths) != N:
39944003
raise ValueError(datashape_message.format("widths"))
39954004

3996-
for pos, width, stats in zip(positions, widths, bxpstats):
4005+
# capwidth
4006+
if capwidths is None:
4007+
capwidths = 0.5 * np.array(widths)
4008+
elif np.isscalar(capwidths):
4009+
capwidths = [capwidths] * N
4010+
elif len(capwidths) != N:
4011+
raise ValueError(datashape_message.format("capwidths"))
4012+
4013+
for pos, width, stats, capwidth in zip(positions, widths, bxpstats,
4014+
capwidths):
39974015
# try to find a new label
39984016
datalabels.append(stats.get('label', pos))
39994017

@@ -4002,8 +4020,8 @@ def do_patch(xs, ys, **kwargs):
40024020
whislo_y = [stats['q1'], stats['whislo']]
40034021
whishi_y = [stats['q3'], stats['whishi']]
40044022
# cap coords
4005-
cap_left = pos - width * 0.25
4006-
cap_right = pos + width * 0.25
4023+
cap_left = pos - capwidth * 0.5
4024+
cap_right = pos + capwidth * 0.5
40074025
cap_x = [cap_left, cap_right]
40084026
cap_lo = np.full(2, stats['whislo'])
40094027
cap_hi = np.full(2, stats['whishi'])
@@ -4013,14 +4031,16 @@ def do_patch(xs, ys, **kwargs):
40134031
med_y = [stats['med'], stats['med']]
40144032
# notched boxes
40154033
if shownotches:
4016-
box_x = [box_left, box_right, box_right, cap_right, box_right,
4017-
box_right, box_left, box_left, cap_left, box_left,
4018-
box_left]
4034+
notch_left = pos - width * 0.25
4035+
notch_right = pos + width * 0.25
4036+
box_x = [box_left, box_right, box_right, notch_right,
4037+
box_right, box_right, box_left, box_left, notch_left,
4038+
box_left, box_left]
40194039
box_y = [stats['q1'], stats['q1'], stats['cilo'],
40204040
stats['med'], stats['cihi'], stats['q3'],
40214041
stats['q3'], stats['cihi'], stats['med'],
40224042
stats['cilo'], stats['q1']]
4023-
med_x = cap_x
4043+
med_x = [notch_left, notch_right]
40244044
# plain boxes
40254045
else:
40264046
box_x = [box_left, box_right, box_right, box_left, box_left]

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,7 +2427,7 @@ def boxplot(
24272427
showfliers=None, boxprops=None, labels=None, flierprops=None,
24282428
medianprops=None, meanprops=None, capprops=None,
24292429
whiskerprops=None, manage_ticks=True, autorange=False,
2430-
zorder=None, *, data=None):
2430+
zorder=None, capwidths=None, *, data=None):
24312431
return gca().boxplot(
24322432
x, notch=notch, sym=sym, vert=vert, whis=whis,
24332433
positions=positions, widths=widths, patch_artist=patch_artist,
@@ -2438,7 +2438,7 @@ def boxplot(
24382438
flierprops=flierprops, medianprops=medianprops,
24392439
meanprops=meanprops, capprops=capprops,
24402440
whiskerprops=whiskerprops, manage_ticks=manage_ticks,
2441-
autorange=autorange, zorder=zorder,
2441+
autorange=autorange, zorder=zorder, capwidths=capwidths,
24422442
**({"data": data} if data is not None else {}))
24432443

24442444

2.79 KB
Loading
2.7 KB
Loading
2.76 KB
Loading

lib/matplotlib/tests/test_axes.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,6 +1675,23 @@ def test_boxplot_dates_pandas(pd):
16751675
plt.boxplot(data, positions=years)
16761676

16771677

1678+
def test_boxplot_capwidths():
1679+
data = np.random.rand(5, 3)
1680+
fig, axs = plt.subplots(9)
1681+
1682+
axs[0].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=[0.1, 0.2, 0.3])
1683+
axs[1].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=0.2)
1684+
axs[2].boxplot(data, capwidths=[0.3, 0.2, 0.1])
1685+
1686+
axs[3].boxplot(data, capwidths=0.5, widths=[0.1, 0.2, 0.3])
1687+
axs[4].boxplot(data, capwidths=0.5, widths=0.2)
1688+
axs[5].boxplot(data, capwidths=0.5)
1689+
1690+
axs[6].boxplot(data, widths=[0.1, 0.2, 0.3])
1691+
axs[7].boxplot(data, widths=0.2)
1692+
axs[8].boxplot(data)
1693+
1694+
16781695
def test_pcolor_regression(pd):
16791696
from pandas.plotting import (
16801697
register_matplotlib_converters,
@@ -2849,6 +2866,25 @@ def test_bxp_bad_positions():
28492866
_bxp_test_helper(bxp_kwargs=dict(positions=[2, 3]))
28502867

28512868

2869+
@image_comparison(['bxp_custom_capwidths.png'],
2870+
savefig_kwarg={'dpi': 40},
2871+
style='default')
2872+
def test_bxp_custom_capwidths():
2873+
_bxp_test_helper(bxp_kwargs=dict(capwidths=[0.0, 0.1, 0.5, 1.0]))
2874+
2875+
2876+
@image_comparison(['bxp_custom_capwidth.png'],
2877+
savefig_kwarg={'dpi': 40},
2878+
style='default')
2879+
def test_bxp_custom_capwidth():
2880+
_bxp_test_helper(bxp_kwargs=dict(capwidths=0.6))
2881+
2882+
2883+
def test_bxp_bad_capwidths():
2884+
with pytest.raises(ValueError):
2885+
_bxp_test_helper(bxp_kwargs=dict(capwidths=[1]))
2886+
2887+
28522888
@image_comparison(['boxplot', 'boxplot'], tol=1.28, style='default')
28532889
def test_boxplot():
28542890
# Randomness used for bootstrapping.
@@ -2868,6 +2904,17 @@ def test_boxplot():
28682904
ax.set_ylim((-30, 30))
28692905

28702906

2907+
@image_comparison(['boxplot_custom_capwidths.png'],
2908+
savefig_kwarg={'dpi': 40}, style='default')
2909+
def test_boxplot_custom_capwidths():
2910+
2911+
x = np.linspace(-7, 7, 140)
2912+
x = np.hstack([-25, x, 25])
2913+
fig, ax = plt.subplots()
2914+
2915+
ax.boxplot([x, x], notch=1, capwidths=[0.01, 0.2])
2916+
2917+
28712918
@image_comparison(['boxplot_sym2.png'], remove_text=True, style='default')
28722919
def test_boxplot_sym2():
28732920
# Randomness used for bootstrapping.

0 commit comments

Comments
 (0)