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

Commit14ddd7e

Browse files
committed
add legend to boxplot
1 parent4ebc8ce commit14ddd7e

File tree

5 files changed

+137
-4
lines changed

5 files changed

+137
-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+
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 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: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3797,7 +3797,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
37973797
labels=None,flierprops=None,medianprops=None,
37983798
meanprops=None,capprops=None,whiskerprops=None,
37993799
manage_ticks=True,autorange=False,zorder=None,
3800-
capwidths=None):
3800+
capwidths=None,label=None):
38013801
"""
38023802
Draw a box and whisker plot.
38033803
@@ -3978,6 +3978,18 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
39783978
The style of the median.
39793979
meanprops : dict, default: None
39803980
The style of the mean.
3981+
label : str or list of str, optional
3982+
Legend labels. Use a single string when all boxes have the same style and
3983+
you only want a single legend entry for them. Use a list of strings to
3984+
label all boxes individually. To be distinguishable, the boxes should be
3985+
styled individually, which is currently only possible by modifying the
3986+
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`.
3987+
3988+
In the case of a single string, the legend entry will technically be
3989+
associated with the first box only. By default, the legend will show the
3990+
median line (``result["medians"]``); if *patch_artist* is True, the legend
3991+
will show the box `Patch` artists (``result["boxes"]``) instead.
3992+
39813993
data : indexable object, optional
39823994
DATA_PARAMETER_PLACEHOLDER
39833995
@@ -4098,7 +4110,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
40984110
meanline=meanline,showfliers=showfliers,
40994111
capprops=capprops,whiskerprops=whiskerprops,
41004112
manage_ticks=manage_ticks,zorder=zorder,
4101-
capwidths=capwidths)
4113+
capwidths=capwidths,label=label)
41024114
returnartists
41034115

41044116
defbxp(self,bxpstats,positions=None,widths=None,vert=True,
@@ -4107,7 +4119,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
41074119
boxprops=None,whiskerprops=None,flierprops=None,
41084120
medianprops=None,capprops=None,meanprops=None,
41094121
meanline=False,manage_ticks=True,zorder=None,
4110-
capwidths=None):
4122+
capwidths=None,label=None):
41114123
"""
41124124
Draw a box and whisker plot from pre-computed statistics.
41134125
@@ -4190,6 +4202,18 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
41904202
If True, the tick locations and labels will be adjusted to match the
41914203
boxplot positions.
41924204
4205+
label : str or list of str, optional
4206+
Legend labels. Use a single string when all boxes have the same style and
4207+
you only want a single legend entry for them. Use a list of strings to
4208+
label all boxes individually. To be distinguishable, the boxes should be
4209+
styled individually, which is currently only possible by modifying the
4210+
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`.
4211+
4212+
In the case of a single string, the legend entry will technically be
4213+
associated with the first box only. By default, the legend will show the
4214+
median line (``result["medians"]``); if *patch_artist* is True, the legend
4215+
will show the box `Patch` artists (``result["boxes"]``) instead.
4216+
41934217
zorder : float, default: ``Line2D.zorder = 2``
41944218
The zorder of the resulting boxplot.
41954219
@@ -4354,6 +4378,7 @@ def do_patch(xs, ys, **kwargs):
43544378
ifshowbox:
43554379
do_box=do_patchifpatch_artistelsedo_plot
43564380
boxes.append(do_box(box_x,box_y,**box_kw))
4381+
median_kw.setdefault('label','_nolegend_')
43574382
# draw the whiskers
43584383
whisker_kw.setdefault('label','_nolegend_')
43594384
whiskers.append(do_plot(whis_x,whislo_y,**whisker_kw))
@@ -4364,7 +4389,6 @@ def do_patch(xs, ys, **kwargs):
43644389
caps.append(do_plot(cap_x,cap_lo,**cap_kw))
43654390
caps.append(do_plot(cap_x,cap_hi,**cap_kw))
43664391
# draw the medians
4367-
median_kw.setdefault('label','_nolegend_')
43684392
medians.append(do_plot(med_x,med_y,**median_kw))
43694393
# maybe draw the means
43704394
ifshowmeans:
@@ -4382,6 +4406,19 @@ def do_patch(xs, ys, **kwargs):
43824406
flier_y=stats['fliers']
43834407
fliers.append(do_plot(flier_x,flier_y,**flier_kw))
43844408

4409+
# Set legend labels
4410+
iflabel:
4411+
box_or_med=boxesifshowboxandpatch_artistelsemedians
4412+
ifcbook.is_scalar_or_string(label):
4413+
# assign the label only to the first box
4414+
box_or_med[0].set_label(label)
4415+
else:# label is a sequence
4416+
iflen(box_or_med)!=len(label):
4417+
raiseValueError("There must be an equal number of legend"
4418+
" labels and boxplots.")
4419+
forartist,lblinzip(box_or_med,label):
4420+
artist.set_label(lbl)
4421+
43854422
ifmanage_ticks:
43864423
axis_name="x"ifvertelse"y"
43874424
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
@@ -2865,6 +2865,7 @@ def boxplot(
28652865
autorange:bool=False,
28662866
zorder:float|None=None,
28672867
capwidths:float|ArrayLike|None=None,
2868+
label:Sequence[str]|None=None,
28682869
*,
28692870
data=None,
28702871
)->dict[str,Any]:
@@ -2896,6 +2897,7 @@ def boxplot(
28962897
autorange=autorange,
28972898
zorder=zorder,
28982899
capwidths=capwidths,
2900+
label=label,
28992901
**({"data":data}ifdataisnotNoneelse {}),
29002902
)
29012903

‎lib/matplotlib/tests/test_legend.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,3 +1450,35 @@ def test_boxplot_labels():
14501450
handles,labels=ax.get_legend_handles_labels()
14511451
assertlen(handles)==0
14521452
assertlen(labels)==0
1453+
1454+
1455+
deftest_boxplot_legend_labels():
1456+
# Test that legend entries are generated when passing `label`.
1457+
np.random.seed(19680801)
1458+
data=np.random.random((10,4))
1459+
fig,axs=plt.subplots(nrows=1,ncols=4)
1460+
legend_labels= ['box A','box B','box C','box D']
1461+
1462+
# Testing legend labels and patch passed to legend.
1463+
bp1=axs[0].boxplot(data,patch_artist=True,label=legend_labels)
1464+
assert [v.get_label()forvinbp1['boxes']]==legend_labels
1465+
handles,labels=axs[0].get_legend_handles_labels()
1466+
assertlabels==legend_labels
1467+
assertall(isinstance(h,mpl.patches.PathPatch)forhinhandles)
1468+
1469+
# Testing legend without `box`.
1470+
bp2=axs[1].boxplot(data,label=legend_labels,showbox=False)
1471+
# Without a box, The legend entries should be passed from the medians.
1472+
assert [v.get_label()forvinbp2['medians']]==legend_labels
1473+
handles,labels=axs[1].get_legend_handles_labels()
1474+
assertlabels==legend_labels
1475+
assertall(isinstance(h,mpl.lines.Line2D)forhinhandles)
1476+
1477+
# Testing legend with number of labels different from number of boxes.
1478+
withpytest.raises(ValueError,match='There must be an equal number'):
1479+
bp3=axs[2].boxplot(data,label=legend_labels[:-1])
1480+
1481+
# Test that for a string label, only the first box gets a label.
1482+
bp4=axs[3].boxplot(data,label='box A')
1483+
assertbp4['medians'][0].get_label()=='box A'
1484+
assertall(x.get_label().startswith("_")forxinbp4['medians'][1:])

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp