-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add legend support for boxplots #27840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
Legend support for Boxplot | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
Boxplots now support a *label* parameter to create legend entries. | ||
|
||
Legend labels can be passed as a list of strings to label multiple boxes in a single | ||
boxplot call: | ||
|
||
|
||
.. plot:: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest to also add a plot for the single-string case. Something like #27840 (comment) - you may switch to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There’s no way around this. Tge individual boxplots do not know about each other and a non-patch_artist, boxplot can only have a line as legend entry, whereas the patch_artist boxplots should have the box. It’s the responsibility of the user to create a consistent plot. So, this behavior is ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just wondering: Shouldn’t the color of the line for data A not be the orange of the median line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it should! For some reason it only does if showbox=False. I didn't notice it before Edit: |
||
:include-source: true | ||
:alt: Example of creating 3 boxplots and assigning legend labels as a sequence. | ||
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
np.random.seed(19680801) | ||
fruit_weights = [ | ||
np.random.normal(130, 10, size=100), | ||
np.random.normal(125, 20, size=100), | ||
np.random.normal(120, 30, size=100), | ||
] | ||
labels = ['peaches', 'oranges', 'tomatoes'] | ||
colors = ['peachpuff', 'orange', 'tomato'] | ||
|
||
fig, ax = plt.subplots() | ||
ax.set_ylabel('fruit weight (g)') | ||
|
||
bplot = ax.boxplot(fruit_weights, | ||
patch_artist=True, # fill with color | ||
label=labels) | ||
|
||
# fill with colors | ||
for patch, color in zip(bplot['boxes'], colors): | ||
patch.set_facecolor(color) | ||
|
||
ax.set_xticks([]) | ||
ax.legend() | ||
timhoffm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
Or as a single string to each individual boxplot: | ||
|
||
.. plot:: | ||
:include-source: true | ||
:alt: Example of creating 2 boxplots and assigning each legend label as a string. | ||
|
||
import matplotlib.pyplot as plt | ||
import numpy as np | ||
|
||
fig, ax = plt.subplots() | ||
timhoffm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
data_A = np.random.random((100, 3)) | ||
data_B = np.random.random((100, 3)) + 0.2 | ||
pos = np.arange(3) | ||
|
||
ax.boxplot(data_A, positions=pos - 0.2, patch_artist=True, label='Box A', | ||
boxprops={'facecolor': 'steelblue'}) | ||
ax.boxplot(data_B, positions=pos + 0.2, patch_artist=True, label='Box B', | ||
boxprops={'facecolor': 'lightblue'}) | ||
|
||
ax.legend() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3798,7 +3798,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, | |
tick_labels=None, flierprops=None, medianprops=None, | ||
meanprops=None, capprops=None, whiskerprops=None, | ||
manage_ticks=True, autorange=False, zorder=None, | ||
capwidths=None): | ||
capwidths=None, label=None): | ||
""" | ||
Draw a box and whisker plot. | ||
|
||
|
@@ -3985,6 +3985,18 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, | |
The style of the median. | ||
meanprops : dict, default: None | ||
The style of the mean. | ||
label : str or list of str, optional | ||
Legend labels. Use a single string when all boxes have the same style and | ||
you only want a single legend entry for them. Use a list of strings to | ||
label all boxes individually. To be distinguishable, the boxes should be | ||
styled individually, which is currently only possible by modifying the | ||
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`. | ||
|
||
In the case of a single string, the legend entry will technically be | ||
associated with the first box only. By default, the legend will show the | ||
median line (``result["medians"]``); if *patch_artist* is True, the legend | ||
will show the box `.Patch` artists (``result["boxes"]``) instead. | ||
|
||
data : indexable object, optional | ||
QuLogic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
DATA_PARAMETER_PLACEHOLDER | ||
|
||
|
@@ -4105,7 +4117,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None, | |
meanline=meanline, showfliers=showfliers, | ||
capprops=capprops, whiskerprops=whiskerprops, | ||
manage_ticks=manage_ticks, zorder=zorder, | ||
capwidths=capwidths) | ||
capwidths=capwidths, label=label) | ||
return artists | ||
|
||
def bxp(self, bxpstats, positions=None, widths=None, vert=True, | ||
|
@@ -4114,7 +4126,7 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, | |
boxprops=None, whiskerprops=None, flierprops=None, | ||
medianprops=None, capprops=None, meanprops=None, | ||
meanline=False, manage_ticks=True, zorder=None, | ||
capwidths=None): | ||
capwidths=None, label=None): | ||
timhoffm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Draw a box and whisker plot from pre-computed statistics. | ||
|
||
|
@@ -4197,6 +4209,18 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True, | |
If True, the tick locations and labels will be adjusted to match the | ||
boxplot positions. | ||
|
||
label : str or list of str, optional | ||
Legend labels. Use a single string when all boxes have the same style and | ||
you only want a single legend entry for them. Use a list of strings to | ||
label all boxes individually. To be distinguishable, the boxes should be | ||
styled individually, which is currently only possible by modifying the | ||
returned artists, see e.g. :doc:`/gallery/statistics/boxplot_demo`. | ||
|
||
In the case of a single string, the legend entry will technically be | ||
associated with the first box only. By default, the legend will show the | ||
median line (``result["medians"]``); if *patch_artist* is True, the legend | ||
will show the box `.Patch` artists (``result["boxes"]``) instead. | ||
|
||
zorder : float, default: ``Line2D.zorder = 2`` | ||
QuLogic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
The zorder of the resulting boxplot. | ||
|
||
|
@@ -4361,6 +4385,7 @@ def do_patch(xs, ys, **kwargs): | |
if showbox: | ||
do_box = do_patch if patch_artist else do_plot | ||
boxes.append(do_box(box_x, box_y, **box_kw)) | ||
median_kw.setdefault('label', '_nolegend_') | ||
# draw the whiskers | ||
whisker_kw.setdefault('label', '_nolegend_') | ||
whiskers.append(do_plot(whis_x, whislo_y, **whisker_kw)) | ||
|
@@ -4371,7 +4396,6 @@ def do_patch(xs, ys, **kwargs): | |
caps.append(do_plot(cap_x, cap_lo, **cap_kw)) | ||
caps.append(do_plot(cap_x, cap_hi, **cap_kw)) | ||
# draw the medians | ||
median_kw.setdefault('label', '_nolegend_') | ||
medians.append(do_plot(med_x, med_y, **median_kw)) | ||
# maybe draw the means | ||
if showmeans: | ||
|
@@ -4389,6 +4413,19 @@ def do_patch(xs, ys, **kwargs): | |
flier_y = stats['fliers'] | ||
fliers.append(do_plot(flier_x, flier_y, **flier_kw)) | ||
|
||
# Set legend labels | ||
if label: | ||
box_or_med = boxes if showbox and patch_artist else medians | ||
if cbook.is_scalar_or_string(label): | ||
# assign the label only to the first box | ||
box_or_med[0].set_label(label) | ||
else: # label is a sequence | ||
if len(box_or_med) != len(label): | ||
raise ValueError("There must be an equal number of legend" | ||
" labels and boxplots.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check should probably be in the "input validation" section, and reuse |
||
for artist, lbl in zip(box_or_med, label): | ||
artist.set_label(lbl) | ||
|
||
if manage_ticks: | ||
axis_name = "x" if vert else "y" | ||
interval = getattr(self.dataLim, f"interval{axis_name}") | ||
|
Uh oh!
There was an error while loading. Please reload this page.