Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5218653

Browse files
authored
Merge pull request #21547 from doctronic/DTTOOL-13
ENH: Custom cap widths in box and whisker plots in bxp() and boxplot()
2 parents d615cf1 + a97de58 commit 5218653

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
@@ -3525,7 +3525,8 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
35253525
showbox=None, showfliers=None, boxprops=None,
35263526
labels=None, flierprops=None, medianprops=None,
35273527
meanprops=None, capprops=None, whiskerprops=None,
3528-
manage_ticks=True, autorange=False, zorder=None):
3528+
manage_ticks=True, autorange=False, zorder=None,
3529+
capwidths=None):
35293530
"""
35303531
Draw a box and whisker plot.
35313532
@@ -3692,6 +3693,8 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
36923693
Show the arithmetic means.
36933694
capprops : dict, default: None
36943695
The style of the caps.
3696+
capwidths : float or array, default: None
3697+
The widths of the caps.
36953698
boxprops : dict, default: None
36963699
The style of the box.
36973700
whiskerprops : dict, default: None
@@ -3820,15 +3823,17 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
38203823
medianprops=medianprops, meanprops=meanprops,
38213824
meanline=meanline, showfliers=showfliers,
38223825
capprops=capprops, whiskerprops=whiskerprops,
3823-
manage_ticks=manage_ticks, zorder=zorder)
3826+
manage_ticks=manage_ticks, zorder=zorder,
3827+
capwidths=capwidths)
38243828
return artists
38253829

38263830
def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38273831
patch_artist=False, shownotches=False, showmeans=False,
38283832
showcaps=True, showbox=True, showfliers=True,
38293833
boxprops=None, whiskerprops=None, flierprops=None,
38303834
medianprops=None, capprops=None, meanprops=None,
3831-
meanline=False, manage_ticks=True, zorder=None):
3835+
meanline=False, manage_ticks=True, zorder=None,
3836+
capwidths=None):
38323837
"""
38333838
Drawing function for box and whisker plots.
38343839
@@ -3866,6 +3871,10 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38663871
The widths of the boxes. The default is
38673872
``clip(0.15*(distance between extreme positions), 0.15, 0.5)``.
38683873
3874+
capwidths : float or array-like, default: None
3875+
Either a scalar or a vector and sets the width of each cap.
3876+
The default is ``0.5*(with of the box)``, see *widths*.
3877+
38693878
vert : bool, default: True
38703879
If `True` (default), makes the boxes vertical.
38713880
If `False`, makes horizontal boxes.
@@ -3998,7 +4007,16 @@ def do_patch(xs, ys, **kwargs):
39984007
elif len(widths) != N:
39994008
raise ValueError(datashape_message.format("widths"))
40004009

4001-
for pos, width, stats in zip(positions, widths, bxpstats):
4010+
# capwidth
4011+
if capwidths is None:
4012+
capwidths = 0.5 * np.array(widths)
4013+
elif np.isscalar(capwidths):
4014+
capwidths = [capwidths] * N
4015+
elif len(capwidths) != N:
4016+
raise ValueError(datashape_message.format("capwidths"))
4017+
4018+
for pos, width, stats, capwidth in zip(positions, widths, bxpstats,
4019+
capwidths):
40024020
# try to find a new label
40034021
datalabels.append(stats.get('label', pos))
40044022

@@ -4007,8 +4025,8 @@ def do_patch(xs, ys, **kwargs):
40074025
whislo_y = [stats['q1'], stats['whislo']]
40084026
whishi_y = [stats['q3'], stats['whishi']]
40094027
# cap coords
4010-
cap_left = pos - width * 0.25
4011-
cap_right = pos + width * 0.25
4028+
cap_left = pos - capwidth * 0.5
4029+
cap_right = pos + capwidth * 0.5
40124030
cap_x = [cap_left, cap_right]
40134031
cap_lo = np.full(2, stats['whislo'])
40144032
cap_hi = np.full(2, stats['whishi'])
@@ -4018,14 +4036,16 @@ def do_patch(xs, ys, **kwargs):
40184036
med_y = [stats['med'], stats['med']]
40194037
# notched boxes
40204038
if shownotches:
4021-
box_x = [box_left, box_right, box_right, cap_right, box_right,
4022-
box_right, box_left, box_left, cap_left, box_left,
4023-
box_left]
4039+
notch_left = pos - width * 0.25
4040+
notch_right = pos + width * 0.25
4041+
box_x = [box_left, box_right, box_right, notch_right,
4042+
box_right, box_right, box_left, box_left, notch_left,
4043+
box_left, box_left]
40244044
box_y = [stats['q1'], stats['q1'], stats['cilo'],
40254045
stats['med'], stats['cihi'], stats['q3'],
40264046
stats['q3'], stats['cihi'], stats['med'],
40274047
stats['cilo'], stats['q1']]
4028-
med_x = cap_x
4048+
med_x = [notch_left, notch_right]
40294049
# plain boxes
40304050
else:
40314051
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
@@ -2431,7 +2431,7 @@ def boxplot(
24312431
showfliers=None, boxprops=None, labels=None, flierprops=None,
24322432
medianprops=None, meanprops=None, capprops=None,
24332433
whiskerprops=None, manage_ticks=True, autorange=False,
2434-
zorder=None, *, data=None):
2434+
zorder=None, capwidths=None, *, data=None):
24352435
return gca().boxplot(
24362436
x, notch=notch, sym=sym, vert=vert, whis=whis,
24372437
positions=positions, widths=widths, patch_artist=patch_artist,
@@ -2442,7 +2442,7 @@ def boxplot(
24422442
flierprops=flierprops, medianprops=medianprops,
24432443
meanprops=meanprops, capprops=capprops,
24442444
whiskerprops=whiskerprops, manage_ticks=manage_ticks,
2445-
autorange=autorange, zorder=zorder,
2445+
autorange=autorange, zorder=zorder, capwidths=capwidths,
24462446
**({"data": data} if data is not None else {}))
24472447

24482448

lib/matplotlib/tests/test_axes.py

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

16851685

1686+
def test_boxplot_capwidths():
1687+
data = np.random.rand(5, 3)
1688+
fig, axs = plt.subplots(9)
1689+
1690+
axs[0].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=[0.1, 0.2, 0.3])
1691+
axs[1].boxplot(data, capwidths=[0.3, 0.2, 0.1], widths=0.2)
1692+
axs[2].boxplot(data, capwidths=[0.3, 0.2, 0.1])
1693+
1694+
axs[3].boxplot(data, capwidths=0.5, widths=[0.1, 0.2, 0.3])
1695+
axs[4].boxplot(data, capwidths=0.5, widths=0.2)
1696+
axs[5].boxplot(data, capwidths=0.5)
1697+
1698+
axs[6].boxplot(data, widths=[0.1, 0.2, 0.3])
1699+
axs[7].boxplot(data, widths=0.2)
1700+
axs[8].boxplot(data)
1701+
1702+
16861703
def test_pcolor_regression(pd):
16871704
from pandas.plotting import (
16881705
register_matplotlib_converters,
@@ -2860,6 +2877,25 @@ def test_bxp_bad_positions():
28602877
_bxp_test_helper(bxp_kwargs=dict(positions=[2, 3]))
28612878

28622879

2880+
@image_comparison(['bxp_custom_capwidths.png'],
2881+
savefig_kwarg={'dpi': 40},
2882+
style='default')
2883+
def test_bxp_custom_capwidths():
2884+
_bxp_test_helper(bxp_kwargs=dict(capwidths=[0.0, 0.1, 0.5, 1.0]))
2885+
2886+
2887+
@image_comparison(['bxp_custom_capwidth.png'],
2888+
savefig_kwarg={'dpi': 40},
2889+
style='default')
2890+
def test_bxp_custom_capwidth():
2891+
_bxp_test_helper(bxp_kwargs=dict(capwidths=0.6))
2892+
2893+
2894+
def test_bxp_bad_capwidths():
2895+
with pytest.raises(ValueError):
2896+
_bxp_test_helper(bxp_kwargs=dict(capwidths=[1]))
2897+
2898+
28632899
@image_comparison(['boxplot', 'boxplot'], tol=1.28, style='default')
28642900
def test_boxplot():
28652901
# Randomness used for bootstrapping.
@@ -2879,6 +2915,17 @@ def test_boxplot():
28792915
ax.set_ylim((-30, 30))
28802916

28812917

2918+
@image_comparison(['boxplot_custom_capwidths.png'],
2919+
savefig_kwarg={'dpi': 40}, style='default')
2920+
def test_boxplot_custom_capwidths():
2921+
2922+
x = np.linspace(-7, 7, 140)
2923+
x = np.hstack([-25, x, 25])
2924+
fig, ax = plt.subplots()
2925+
2926+
ax.boxplot([x, x], notch=1, capwidths=[0.01, 0.2])
2927+
2928+
28822929
@image_comparison(['boxplot_sym2.png'], remove_text=True, style='default')
28832930
def test_boxplot_sym2():
28842931
# Randomness used for bootstrapping.

0 commit comments

Comments
 (0)