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

Skip to content

Commit 9c033d2

Browse files
committed
add legend support for boxplots
1 parent 76eaa96 commit 9c033d2

File tree

8 files changed

+225
-54
lines changed

8 files changed

+225
-54
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``boxplot`` legend labels
2+
~~~~~~~~~~~~~~~~~~~~~~~~~
3+
The tick labels on `~.Axes.boxplot` were previously set with the `labels` parameter.
4+
This has been changed to `tick_labels` to be consistent with `~.Axes.bar` and to
5+
accommodate the newly introduced `label` parameter for the legend labels.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Legend support for Boxplot
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Boxplots now generate legend entries and can be labelled with the
4+
new `label` parameter. If a patch is passed to the box with `show_patch=True`,
5+
the legend gets its handle from the patch instead of the `.Line2D` object from
6+
the whiskers.
7+
The old `labels` parameter that was used for setting tick labels is deprecated
8+
and replaced with `tick_labels`.
9+
10+
.. plot::
11+
:include-source: true
12+
:alt: Example of creating 3 boxplots and assigning legend labels and tick labels with keywords.
13+
14+
import matplotlib.pyplot as plt
15+
import numpy as np
16+
17+
np.random.seed(19680801)
18+
fruit_weights = [
19+
np.random.normal(130, 10, size=100),
20+
np.random.normal(125, 20, size=100),
21+
np.random.normal(120, 30, size=100),
22+
]
23+
labels = ['peaches', 'oranges', 'tomatoes']
24+
colors = ['peachpuff', 'orange', 'tomato']
25+
tick_lb = ['A', 'B', 'C']
26+
27+
fig, ax = plt.subplots()
28+
ax.set_ylabel('fruit weight (g)')
29+
30+
bplot = ax.boxplot(fruit_weights,
31+
patch_artist=True, # fill with color
32+
tick_labels=tick_lb,
33+
label=labels)
34+
35+
# fill with colors
36+
for patch, color in zip(bplot['boxes'], colors):
37+
patch.set_facecolor(color)
38+
39+
ax.legend()
40+
plt.show()

lib/matplotlib/axes/_axes.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3765,15 +3765,16 @@ def apply_mask(arrays, mask):
37653765
return errorbar_container # (l0, caplines, barcols)
37663766

37673767
@_preprocess_data()
3768+
@_api.rename_parameter("3.9", "labels", "tick_labels")
37683769
def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
37693770
positions=None, widths=None, patch_artist=None,
37703771
bootstrap=None, usermedians=None, conf_intervals=None,
37713772
meanline=None, showmeans=None, showcaps=None,
37723773
showbox=None, showfliers=None, boxprops=None,
3773-
labels=None, flierprops=None, medianprops=None,
3774+
tick_labels=None, flierprops=None, medianprops=None,
37743775
meanprops=None, capprops=None, whiskerprops=None,
37753776
manage_ticks=True, autorange=False, zorder=None,
3776-
capwidths=None):
3777+
capwidths=None, label=None):
37773778
"""
37783779
Draw a box and whisker plot.
37793780
@@ -3884,10 +3885,12 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
38843885
If `False` produces boxes with the Line2D artist. Otherwise,
38853886
boxes are drawn with Patch artists.
38863887
3887-
labels : sequence, optional
3888+
tick_labels : sequence, optional
38883889
Labels for each dataset (one per dataset). These are used for
38893890
x-tick labels; *not* for legend entries.
38903891
3892+
.. versionadded:: 3.9
3893+
38913894
manage_ticks : bool, default: True
38923895
If True, the tick locations and labels will be adjusted to match
38933896
the boxplot positions.
@@ -3956,6 +3959,10 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
39563959
The style of the mean.
39573960
data : indexable object, optional
39583961
DATA_PARAMETER_PLACEHOLDER
3962+
label : sequence, optional
3963+
Legend labels for each boxplot.
3964+
3965+
.. versionadded:: 3.9
39593966
39603967
See Also
39613968
--------
@@ -3970,7 +3977,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
39703977
bootstrap = mpl.rcParams['boxplot.bootstrap']
39713978

39723979
bxpstats = cbook.boxplot_stats(x, whis=whis, bootstrap=bootstrap,
3973-
labels=labels, autorange=autorange)
3980+
tick_labels=tick_labels, autorange=autorange)
39743981
if notch is None:
39753982
notch = mpl.rcParams['boxplot.notch']
39763983
if vert is None:
@@ -4006,6 +4013,9 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
40064013
if 'color' in boxprops:
40074014
boxprops['edgecolor'] = boxprops.pop('color')
40084015

4016+
if label:
4017+
boxprops['label'] = label
4018+
40094019
# if non-default sym value, put it into the flier dictionary
40104020
# the logic for providing the default symbol ('b+') now lives
40114021
# in bxp in the initial value of flierkw
@@ -4074,7 +4084,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
40744084
meanline=meanline, showfliers=showfliers,
40754085
capprops=capprops, whiskerprops=whiskerprops,
40764086
manage_ticks=manage_ticks, zorder=zorder,
4077-
capwidths=capwidths)
4087+
capwidths=capwidths, label=label)
40784088
return artists
40794089

40804090
def bxp(self, bxpstats, positions=None, widths=None, vert=True,
@@ -4083,7 +4093,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
40834093
boxprops=None, whiskerprops=None, flierprops=None,
40844094
medianprops=None, capprops=None, meanprops=None,
40854095
meanline=False, manage_ticks=True, zorder=None,
4086-
capwidths=None):
4096+
capwidths=None, label=None):
40874097
"""
40884098
Draw a box and whisker plot from pre-computed statistics.
40894099
@@ -4169,6 +4179,9 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
41694179
zorder : float, default: ``Line2D.zorder = 2``
41704180
The zorder of the resulting boxplot.
41714181
4182+
label : sequence, optional
4183+
Legend labels for each boxplot.
4184+
41724185
Returns
41734186
-------
41744187
dict
@@ -4330,8 +4343,8 @@ def do_patch(xs, ys, **kwargs):
43304343
if showbox:
43314344
do_box = do_patch if patch_artist else do_plot
43324345
boxes.append(do_box(box_x, box_y, **box_kw))
4346+
whisker_kw.setdefault('label', '_nolegend_')
43334347
# draw the whiskers
4334-
whisker_kw.setdefault('label', '_nolegend_')
43354348
whiskers.append(do_plot(whis_x, whislo_y, **whisker_kw))
43364349
whiskers.append(do_plot(whis_x, whishi_y, **whisker_kw))
43374350
# maybe draw the caps
@@ -4358,6 +4371,32 @@ def do_patch(xs, ys, **kwargs):
43584371
flier_y = stats['fliers']
43594372
fliers.append(do_plot(flier_x, flier_y, **flier_kw))
43604373

4374+
# Set legend labels
4375+
if boxprops:
4376+
if 'label' in boxprops:
4377+
# If showbox=False, label will be passed to whiskers instead.
4378+
box_or_whis = boxes if showbox else whiskers
4379+
for index, element in enumerate(box_or_whis):
4380+
try:
4381+
if isinstance(boxprops['label'], list):
4382+
element.set_label(boxprops['label'][index])
4383+
else:
4384+
element.set_label(boxprops['label'])
4385+
# If only one label is passed, stop it from being repeated
4386+
# on each legend handle.
4387+
boxprops['label'] = ''
4388+
except Exception:
4389+
IndexError('List index out of range')
4390+
# If boxplot is created from a call to `ax.bxp(label=...)`, the boxprops will
4391+
# not be set and the element will get the legend label from `bxp` kwargs.
4392+
elif label:
4393+
box_or_whis = boxes if showbox else whiskers
4394+
for index, element in enumerate(box_or_whis):
4395+
try:
4396+
element.set_label(label[index])
4397+
except Exception:
4398+
IndexError('list index out of range')
4399+
43614400
if manage_ticks:
43624401
axis_name = "x" if vert else "y"
43634402
interval = getattr(self.dataLim, f"interval{axis_name}")

lib/matplotlib/axes/_axes.pyi

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ class Axes(_AxesBase):
360360
showbox: bool | None = ...,
361361
showfliers: bool | None = ...,
362362
boxprops: dict[str, Any] | None = ...,
363-
labels: Sequence[str] | None = ...,
363+
tick_labels: Sequence[str] | None = ...,
364364
flierprops: dict[str, Any] | None = ...,
365365
medianprops: dict[str, Any] | None = ...,
366366
meanprops: dict[str, Any] | None = ...,
@@ -370,6 +370,7 @@ class Axes(_AxesBase):
370370
autorange: bool = ...,
371371
zorder: float | None = ...,
372372
capwidths: float | ArrayLike | None = ...,
373+
label: Sequence[str] | None = ...,
373374
*,
374375
data=...,
375376
) -> dict[str, Any]: ...
@@ -395,6 +396,7 @@ class Axes(_AxesBase):
395396
manage_ticks: bool = ...,
396397
zorder: float | None = ...,
397398
capwidths: float | ArrayLike | None = ...,
399+
label: Sequence[str] | None = ...,
398400
) -> dict[str, Any]: ...
399401
def scatter(
400402
self,

lib/matplotlib/cbook.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,8 @@ def _broadcast_with_masks(*args, compress=False):
11261126
return inputs
11271127

11281128

1129-
def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None,
1129+
@_api.rename_parameter("3.9", "labels", "tick_labels")
1130+
def boxplot_stats(X, whis=1.5, bootstrap=None, tick_labels=None,
11301131
autorange=False):
11311132
r"""
11321133
Return a list of dictionaries of statistics used to draw a series of box
@@ -1161,10 +1162,12 @@ def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None,
11611162
Number of times the confidence intervals around the median
11621163
should be bootstrapped (percentile method).
11631164
1164-
labels : array-like, optional
1165+
tick_labels : array-like, optional
11651166
Labels for each dataset. Length must be compatible with
11661167
dimensions of *X*.
11671168
1169+
.. versionadded:: 3.9
1170+
11681171
autorange : bool, optional (False)
11691172
When `True` and the data are distributed such that the 25th and 75th
11701173
percentiles are equal, ``whis`` is set to (0, 100) such that the
@@ -1240,13 +1243,13 @@ def _compute_conf_interval(data, med, iqr, bootstrap):
12401243
X = _reshape_2D(X, "X")
12411244

12421245
ncols = len(X)
1243-
if labels is None:
1244-
labels = itertools.repeat(None)
1245-
elif len(labels) != ncols:
1246+
if tick_labels is None:
1247+
tick_labels = itertools.repeat(None)
1248+
elif len(tick_labels) != ncols:
12461249
raise ValueError("Dimensions of labels and X must be compatible")
12471250

12481251
input_whis = whis
1249-
for ii, (x, label) in enumerate(zip(X, labels)):
1252+
for ii, (x, label) in enumerate(zip(X, tick_labels)):
12501253

12511254
# empty dict
12521255
stats = {}

lib/matplotlib/cbook.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def boxplot_stats(
135135
X: ArrayLike,
136136
whis: float | tuple[float, float] = ...,
137137
bootstrap: int | None = ...,
138-
labels: ArrayLike | None = ...,
138+
tick_labels: ArrayLike | None = ...,
139139
autorange: bool = ...,
140140
) -> list[dict[str, Any]]: ...
141141

lib/matplotlib/pyplot.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2667,17 +2667,21 @@ def annotate(
26672667
text: str,
26682668
xy: tuple[float, float],
26692669
xytext: tuple[float, float] | None = None,
2670-
xycoords: str
2671-
| Artist
2672-
| Transform
2673-
| Callable[[RendererBase], Bbox | Transform]
2674-
| tuple[float, float] = "data",
2675-
textcoords: str
2676-
| Artist
2677-
| Transform
2678-
| Callable[[RendererBase], Bbox | Transform]
2679-
| tuple[float, float]
2680-
| None = None,
2670+
xycoords: (
2671+
str
2672+
| Artist
2673+
| Transform
2674+
| Callable[[RendererBase], Bbox | Transform]
2675+
| tuple[float, float]
2676+
) = "data",
2677+
textcoords: (
2678+
str
2679+
| Artist
2680+
| Transform
2681+
| Callable[[RendererBase], Bbox | Transform]
2682+
| tuple[float, float]
2683+
| None
2684+
) = None,
26812685
arrowprops: dict[str, Any] | None = None,
26822686
annotation_clip: bool | None = None,
26832687
**kwargs,
@@ -2855,7 +2859,7 @@ def boxplot(
28552859
showbox: bool | None = None,
28562860
showfliers: bool | None = None,
28572861
boxprops: dict[str, Any] | None = None,
2858-
labels: Sequence[str] | None = None,
2862+
tick_labels: Sequence[str] | None = None,
28592863
flierprops: dict[str, Any] | None = None,
28602864
medianprops: dict[str, Any] | None = None,
28612865
meanprops: dict[str, Any] | None = None,
@@ -2865,6 +2869,7 @@ def boxplot(
28652869
autorange: bool = False,
28662870
zorder: float | None = None,
28672871
capwidths: float | ArrayLike | None = None,
2872+
label: Sequence[str] | None = None,
28682873
*,
28692874
data=None,
28702875
) -> dict[str, Any]:
@@ -2886,7 +2891,7 @@ def boxplot(
28862891
showbox=showbox,
28872892
showfliers=showfliers,
28882893
boxprops=boxprops,
2889-
labels=labels,
2894+
tick_labels=tick_labels,
28902895
flierprops=flierprops,
28912896
medianprops=medianprops,
28922897
meanprops=meanprops,
@@ -2896,6 +2901,7 @@ def boxplot(
28962901
autorange=autorange,
28972902
zorder=zorder,
28982903
capwidths=capwidths,
2904+
label=label,
28992905
**({"data": data} if data is not None else {}),
29002906
)
29012907

@@ -2928,8 +2934,9 @@ def cohere(
29282934
NFFT: int = 256,
29292935
Fs: float = 2,
29302936
Fc: int = 0,
2931-
detrend: Literal["none", "mean", "linear"]
2932-
| Callable[[ArrayLike], ArrayLike] = mlab.detrend_none,
2937+
detrend: (
2938+
Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike]
2939+
) = mlab.detrend_none,
29332940
window: Callable[[ArrayLike], ArrayLike] | ArrayLike = mlab.window_hanning,
29342941
noverlap: int = 0,
29352942
pad_to: int | None = None,
@@ -2986,9 +2993,9 @@ def csd(
29862993
NFFT: int | None = None,
29872994
Fs: float | None = None,
29882995
Fc: int | None = None,
2989-
detrend: Literal["none", "mean", "linear"]
2990-
| Callable[[ArrayLike], ArrayLike]
2991-
| None = None,
2996+
detrend: (
2997+
Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
2998+
) = None,
29922999
window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
29933000
noverlap: int | None = None,
29943001
pad_to: int | None = None,
@@ -3651,9 +3658,9 @@ def psd(
36513658
NFFT: int | None = None,
36523659
Fs: float | None = None,
36533660
Fc: int | None = None,
3654-
detrend: Literal["none", "mean", "linear"]
3655-
| Callable[[ArrayLike], ArrayLike]
3656-
| None = None,
3661+
detrend: (
3662+
Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
3663+
) = None,
36573664
window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
36583665
noverlap: int | None = None,
36593666
pad_to: int | None = None,
@@ -3759,9 +3766,9 @@ def specgram(
37593766
NFFT: int | None = None,
37603767
Fs: float | None = None,
37613768
Fc: int | None = None,
3762-
detrend: Literal["none", "mean", "linear"]
3763-
| Callable[[ArrayLike], ArrayLike]
3764-
| None = None,
3769+
detrend: (
3770+
Literal["none", "mean", "linear"] | Callable[[ArrayLike], ArrayLike] | None
3771+
) = None,
37653772
window: Callable[[ArrayLike], ArrayLike] | ArrayLike | None = None,
37663773
noverlap: int | None = None,
37673774
cmap: str | Colormap | None = None,
@@ -4071,10 +4078,9 @@ def violinplot(
40714078
showmedians: bool = False,
40724079
quantiles: Sequence[float | Sequence[float]] | None = None,
40734080
points: int = 100,
4074-
bw_method: Literal["scott", "silverman"]
4075-
| float
4076-
| Callable[[GaussianKDE], float]
4077-
| None = None,
4081+
bw_method: (
4082+
Literal["scott", "silverman"] | float | Callable[[GaussianKDE], float] | None
4083+
) = None,
40784084
*,
40794085
data=None,
40804086
) -> dict[str, Collection]:

0 commit comments

Comments
 (0)