Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit102d0a6

Browse files
authored
Merge pull request#27840 from saranti/box_legend_support
Add legend support for boxplots
2 parents1174aad +c96334d commit102d0a6

File tree

5 files changed

+140
-4
lines changed

5 files changed

+140
-4
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
Legend support for Boxplot
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Boxplots now support a *label* parameter to create legend entries.
4+
5+
Legend labels can be passed as a list of strings to label multiple boxes in a single
6+
`.Axes.boxplot` call:
7+
8+
9+
..plot::
10+
:include-source: true
11+
:alt: Example of creating 3 boxplots and assigning legend labels as a sequence.
12+
13+
import matplotlib.pyplot as plt
14+
import numpy as np
15+
16+
np.random.seed(19680801)
17+
fruit_weights = [
18+
np.random.normal(130, 10, size=100),
19+
np.random.normal(125, 20, size=100),
20+
np.random.normal(120, 30, size=100),
21+
]
22+
labels = ['peaches', 'oranges', 'tomatoes']
23+
colors = ['peachpuff', 'orange', 'tomato']
24+
25+
fig, ax = plt.subplots()
26+
ax.set_ylabel('fruit weight (g)')
27+
28+
bplot = ax.boxplot(fruit_weights,
29+
patch_artist=True, # fill with color
30+
label=labels)
31+
32+
# fill with colors
33+
for patch, color in zip(bplot['boxes'], colors):
34+
patch.set_facecolor(color)
35+
36+
ax.set_xticks([])
37+
ax.legend()
38+
39+
40+
Or as a single string to each individual `.Axes.boxplot`:
41+
42+
..plot::
43+
:include-source: true
44+
:alt: Example of creating 2 boxplots and assigning each legend label as a string.
45+
46+
import matplotlib.pyplot as plt
47+
import numpy as np
48+
49+
fig, ax = plt.subplots()
50+
51+
data_A = np.random.random((100, 3))
52+
data_B = np.random.random((100, 3)) + 0.2
53+
pos = np.arange(3)
54+
55+
ax.boxplot(data_A, positions=pos - 0.2, patch_artist=True, label='Box A',
56+
boxprops={'facecolor': 'steelblue'})
57+
ax.boxplot(data_B, positions=pos + 0.2, patch_artist=True, label='Box B',
58+
boxprops={'facecolor': 'lightblue'})
59+
60+
ax.legend()

‎lib/matplotlib/axes/_axes.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3816,7 +3816,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
38163816
tick_labels=None,flierprops=None,medianprops=None,
38173817
meanprops=None,capprops=None,whiskerprops=None,
38183818
manage_ticks=True,autorange=False,zorder=None,
3819-
capwidths=None):
3819+
capwidths=None,label=None):
38203820
"""
38213821
Draw a box and whisker plot.
38223822
@@ -4003,6 +4003,20 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
40034003
The style of the median.
40044004
meanprops : dict, default: None
40054005
The style of the mean.
4006+
label : str or list of str, optional
4007+
Legend labels. Use a single string when all boxes have the same style and
4008+
you only want a single legend entry for them. Use a list of strings to
4009+
label all boxes individually. To be distinguishable, the boxes should be
4010+
styled individually, which is currently only possible by modifying the
4011+
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`.
4012+
4013+
In the case of a single string, the legend entry will technically be
4014+
associated with the first box only. By default, the legend will show the
4015+
median line (``result["medians"]``); if *patch_artist* is True, the legend
4016+
will show the box `.Patch` artists (``result["boxes"]``) instead.
4017+
4018+
.. versionadded:: 3.9
4019+
40064020
data : indexable object, optional
40074021
DATA_PARAMETER_PLACEHOLDER
40084022
@@ -4123,7 +4137,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
41234137
meanline=meanline,showfliers=showfliers,
41244138
capprops=capprops,whiskerprops=whiskerprops,
41254139
manage_ticks=manage_ticks,zorder=zorder,
4126-
capwidths=capwidths)
4140+
capwidths=capwidths,label=label)
41274141
returnartists
41284142

41294143
defbxp(self,bxpstats,positions=None,widths=None,vert=True,
@@ -4132,7 +4146,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
41324146
boxprops=None,whiskerprops=None,flierprops=None,
41334147
medianprops=None,capprops=None,meanprops=None,
41344148
meanline=False,manage_ticks=True,zorder=None,
4135-
capwidths=None):
4149+
capwidths=None,label=None):
41364150
"""
41374151
Draw a box and whisker plot from pre-computed statistics.
41384152
@@ -4215,6 +4229,20 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
42154229
If True, the tick locations and labels will be adjusted to match the
42164230
boxplot positions.
42174231
4232+
label : str or list of str, optional
4233+
Legend labels. Use a single string when all boxes have the same style and
4234+
you only want a single legend entry for them. Use a list of strings to
4235+
label all boxes individually. To be distinguishable, the boxes should be
4236+
styled individually, which is currently only possible by modifying the
4237+
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`.
4238+
4239+
In the case of a single string, the legend entry will technically be
4240+
associated with the first box only. By default, the legend will show the
4241+
median line (``result["medians"]``); if *patch_artist* is True, the legend
4242+
will show the box `.Patch` artists (``result["boxes"]``) instead.
4243+
4244+
.. versionadded:: 3.9
4245+
42184246
zorder : float, default: ``Line2D.zorder = 2``
42194247
The zorder of the resulting boxplot.
42204248
@@ -4379,6 +4407,7 @@ def do_patch(xs, ys, **kwargs):
43794407
ifshowbox:
43804408
do_box=do_patchifpatch_artistelsedo_plot
43814409
boxes.append(do_box(box_x,box_y,**box_kw))
4410+
median_kw.setdefault('label','_nolegend_')
43824411
# draw the whiskers
43834412
whisker_kw.setdefault('label','_nolegend_')
43844413
whiskers.append(do_plot(whis_x,whislo_y,**whisker_kw))
@@ -4389,7 +4418,6 @@ def do_patch(xs, ys, **kwargs):
43894418
caps.append(do_plot(cap_x,cap_lo,**cap_kw))
43904419
caps.append(do_plot(cap_x,cap_hi,**cap_kw))
43914420
# draw the medians
4392-
median_kw.setdefault('label','_nolegend_')
43934421
medians.append(do_plot(med_x,med_y,**median_kw))
43944422
# maybe draw the means
43954423
ifshowmeans:
@@ -4407,6 +4435,18 @@ def do_patch(xs, ys, **kwargs):
44074435
flier_y=stats['fliers']
44084436
fliers.append(do_plot(flier_x,flier_y,**flier_kw))
44094437

4438+
# Set legend labels
4439+
iflabel:
4440+
box_or_med=boxesifshowboxandpatch_artistelsemedians
4441+
ifcbook.is_scalar_or_string(label):
4442+
# assign the label only to the first box
4443+
box_or_med[0].set_label(label)
4444+
else:# label is a sequence
4445+
iflen(box_or_med)!=len(label):
4446+
raiseValueError(datashape_message.format("label"))
4447+
forartist,lblinzip(box_or_med,label):
4448+
artist.set_label(lbl)
4449+
44104450
ifmanage_ticks:
44114451
axis_name="x"ifvertelse"y"
44124452
interval=getattr(self.dataLim,f"interval{axis_name}")

‎lib/matplotlib/axes/_axes.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ class Axes(_AxesBase):
372372
autorange:bool= ...,
373373
zorder:float|None= ...,
374374
capwidths:float|ArrayLike|None= ...,
375+
label:Sequence[str]|None= ...,
375376
*,
376377
data=...,
377378
)->dict[str,Any]: ...
@@ -397,6 +398,7 @@ class Axes(_AxesBase):
397398
manage_ticks:bool= ...,
398399
zorder:float|None= ...,
399400
capwidths:float|ArrayLike|None= ...,
401+
label:Sequence[str]|None= ...,
400402
)->dict[str,Any]: ...
401403
defscatter(
402404
self,

‎lib/matplotlib/pyplot.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2941,6 +2941,7 @@ def boxplot(
29412941
autorange:bool=False,
29422942
zorder:float|None=None,
29432943
capwidths:float|ArrayLike|None=None,
2944+
label:Sequence[str]|None=None,
29442945
*,
29452946
data=None,
29462947
)->dict[str,Any]:
@@ -2972,6 +2973,7 @@ def boxplot(
29722973
autorange=autorange,
29732974
zorder=zorder,
29742975
capwidths=capwidths,
2976+
label=label,
29752977
**({"data":data}ifdataisnotNoneelse {}),
29762978
)
29772979

‎lib/matplotlib/tests/test_legend.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,3 +1435,35 @@ def test_legend_text():
14351435
leg_bboxes.append(
14361436
leg.get_window_extent().transformed(ax.transAxes.inverted()))
14371437
assert_allclose(leg_bboxes[1].bounds,leg_bboxes[0].bounds)
1438+
1439+
1440+
deftest_boxplot_legend_labels():
1441+
# Test that legend entries are generated when passing `label`.
1442+
np.random.seed(19680801)
1443+
data=np.random.random((10,4))
1444+
fig,axs=plt.subplots(nrows=1,ncols=4)
1445+
legend_labels= ['box A','box B','box C','box D']
1446+
1447+
# Testing legend labels and patch passed to legend.
1448+
bp1=axs[0].boxplot(data,patch_artist=True,label=legend_labels)
1449+
assert [v.get_label()forvinbp1['boxes']]==legend_labels
1450+
handles,labels=axs[0].get_legend_handles_labels()
1451+
assertlabels==legend_labels
1452+
assertall(isinstance(h,mpl.patches.PathPatch)forhinhandles)
1453+
1454+
# Testing legend without `box`.
1455+
bp2=axs[1].boxplot(data,label=legend_labels,showbox=False)
1456+
# Without a box, The legend entries should be passed from the medians.
1457+
assert [v.get_label()forvinbp2['medians']]==legend_labels
1458+
handles,labels=axs[1].get_legend_handles_labels()
1459+
assertlabels==legend_labels
1460+
assertall(isinstance(h,mpl.lines.Line2D)forhinhandles)
1461+
1462+
# Testing legend with number of labels different from number of boxes.
1463+
withpytest.raises(ValueError,match='values must have same the length'):
1464+
bp3=axs[2].boxplot(data,label=legend_labels[:-1])
1465+
1466+
# Test that for a string label, only the first box gets a label.
1467+
bp4=axs[3].boxplot(data,label='box A')
1468+
assertbp4['medians'][0].get_label()=='box A'
1469+
assertall(x.get_label().startswith("_")forxinbp4['medians'][1:])

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp