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

Skip to content

Commit b87182d

Browse files
authored
Merge pull request #13444 from anntzer/boxplot-multiple
Allow constructing boxplots over multiple calls.
2 parents 657e158 + f35e980 commit b87182d

File tree

5 files changed

+70
-30
lines changed

5 files changed

+70
-30
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
API changes
2+
```````````
3+
4+
The ``manage_xticks`` parameter of `~Axes.boxplot` and `~Axes.bxp` has been
5+
renamed (with a deprecation period) to ``manage_ticks``, to take into account
6+
the fact that it manages either x or y ticks depending on the ``vert``
7+
parameter.
8+
9+
When ``manage_ticks`` is True (the default), these methods now attempt to take
10+
previously drawn boxplots into account when setting the axis limits, ticks, and
11+
tick labels.

lib/matplotlib/axes/_axes.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3429,6 +3429,7 @@ def extract_err(err, data):
34293429

34303430
return errorbar_container # (l0, caplines, barcols)
34313431

3432+
@cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
34323433
@_preprocess_data()
34333434
def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
34343435
positions=None, widths=None, patch_artist=None,
@@ -3437,7 +3438,7 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
34373438
showbox=None, showfliers=None, boxprops=None,
34383439
labels=None, flierprops=None, medianprops=None,
34393440
meanprops=None, capprops=None, whiskerprops=None,
3440-
manage_xticks=True, autorange=False, zorder=None):
3441+
manage_ticks=True, autorange=False, zorder=None):
34413442
"""
34423443
Make a box and whisker plot.
34433444
@@ -3538,8 +3539,9 @@ def boxplot(self, x, notch=None, sym=None, vert=None, whis=None,
35383539
Labels for each dataset. Length must be compatible with
35393540
dimensions of ``x``.
35403541
3541-
manage_xticks : bool, optional (True)
3542-
If the function should adjust the xlim and xtick locations.
3542+
manage_ticks : bool, optional (True)
3543+
If True, the tick locations and labels will be adjusted to match
3544+
the boxplot positions.
35433545
35443546
autorange : bool, optional (False)
35453547
When `True` and the data are distributed such that the 25th and
@@ -3729,15 +3731,16 @@ def _update_dict(dictionary, rc_name, properties):
37293731
medianprops=medianprops, meanprops=meanprops,
37303732
meanline=meanline, showfliers=showfliers,
37313733
capprops=capprops, whiskerprops=whiskerprops,
3732-
manage_xticks=manage_xticks, zorder=zorder)
3734+
manage_ticks=manage_ticks, zorder=zorder)
37333735
return artists
37343736

3737+
@cbook._rename_parameter("3.1", "manage_xticks", "manage_ticks")
37353738
def bxp(self, bxpstats, positions=None, widths=None, vert=True,
37363739
patch_artist=False, shownotches=False, showmeans=False,
37373740
showcaps=True, showbox=True, showfliers=True,
37383741
boxprops=None, whiskerprops=None, flierprops=None,
37393742
medianprops=None, capprops=None, meanprops=None,
3740-
meanline=False, manage_xticks=True, zorder=None):
3743+
meanline=False, manage_ticks=True, zorder=None):
37413744
"""
37423745
Drawing function for box and whisker plots.
37433746
@@ -3841,11 +3844,12 @@ def bxp(self, bxpstats, positions=None, widths=None, vert=True,
38413844
*meanprops*. Not recommended if *shownotches* is also True.
38423845
Otherwise, means will be shown as points.
38433846
3844-
manage_xticks : bool, default = True
3845-
If the function should adjust the xlim and xtick locations.
3847+
manage_ticks : bool, default = True
3848+
If True, the tick locations and labels will be adjusted to match the
3849+
boxplot positions.
38463850
3847-
zorder : scalar, default = None
3848-
The zorder of the resulting boxplot
3851+
zorder : scalar, default = None
3852+
The zorder of the resulting boxplot.
38493853
38503854
Returns
38513855
-------
@@ -4117,21 +4121,38 @@ def dopatch(xs, ys, **kwargs):
41174121
flier_x, flier_y, **final_flierprops
41184122
))
41194123

4120-
# fix our axes/ticks up a little
4121-
if vert:
4122-
setticks = self.set_xticks
4123-
setlim = self.set_xlim
4124-
setlabels = self.set_xticklabels
4125-
else:
4126-
setticks = self.set_yticks
4127-
setlim = self.set_ylim
4128-
setlabels = self.set_yticklabels
4129-
4130-
if manage_xticks:
4131-
newlimits = min(positions) - 0.5, max(positions) + 0.5
4132-
setlim(newlimits)
4133-
setticks(positions)
4134-
setlabels(datalabels)
4124+
if manage_ticks:
4125+
axis_name = "x" if vert else "y"
4126+
interval = getattr(self.dataLim, f"interval{axis_name}")
4127+
axis = getattr(self, f"{axis_name}axis")
4128+
positions = axis.convert_units(positions)
4129+
# The 0.5 additional padding ensures reasonable-looking boxes
4130+
# even when drawing a single box. We set the sticky edge to
4131+
# prevent margins expansion, in order to match old behavior (back
4132+
# when separate calls to boxplot() would completely reset the axis
4133+
# limits regardless of what was drawn before). The sticky edges
4134+
# are attached to the median lines, as they are always present.
4135+
interval[:] = (min(interval[0], min(positions) - .5),
4136+
max(interval[1], max(positions) + .5))
4137+
for median, position in zip(medians, positions):
4138+
getattr(median.sticky_edges, axis_name).extend(
4139+
[position - .5, position + .5])
4140+
# Modified from Axis.set_ticks and Axis.set_ticklabels.
4141+
locator = axis.get_major_locator()
4142+
if not isinstance(axis.get_major_locator(),
4143+
mticker.FixedLocator):
4144+
locator = mticker.FixedLocator([])
4145+
axis.set_major_locator(locator)
4146+
locator.locs = np.array([*locator.locs, *positions])
4147+
formatter = axis.get_major_formatter()
4148+
if not isinstance(axis.get_major_formatter(),
4149+
mticker.FixedFormatter):
4150+
formatter = mticker.FixedFormatter([])
4151+
axis.set_major_formatter(formatter)
4152+
formatter.seq = [*formatter.seq, *datalabels]
4153+
4154+
self.autoscale_view(
4155+
scalex=self._autoscaleXon, scaley=self._autoscaleYon)
41354156

41364157
return dict(whiskers=whiskers, caps=caps, boxes=boxes,
41374158
medians=medians, fliers=fliers, means=means)

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,7 +2436,7 @@ def boxplot(
24362436
meanline=None, showmeans=None, showcaps=None, showbox=None,
24372437
showfliers=None, boxprops=None, labels=None, flierprops=None,
24382438
medianprops=None, meanprops=None, capprops=None,
2439-
whiskerprops=None, manage_xticks=True, autorange=False,
2439+
whiskerprops=None, manage_ticks=True, autorange=False,
24402440
zorder=None, *, data=None):
24412441
return gca().boxplot(
24422442
x, notch=notch, sym=sym, vert=vert, whis=whis,
@@ -2447,7 +2447,7 @@ def boxplot(
24472447
showfliers=showfliers, boxprops=boxprops, labels=labels,
24482448
flierprops=flierprops, medianprops=medianprops,
24492449
meanprops=meanprops, capprops=capprops,
2450-
whiskerprops=whiskerprops, manage_xticks=manage_xticks,
2450+
whiskerprops=whiskerprops, manage_ticks=manage_ticks,
24512451
autorange=autorange, zorder=zorder, **({"data": data} if data
24522452
is not None else {}))
24532453

lib/matplotlib/tests/test_axes.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2911,12 +2911,21 @@ def test_manage_xticks():
29112911
np.random.seed(0)
29122912
y1 = np.random.normal(10, 3, 20)
29132913
y2 = np.random.normal(3, 1, 20)
2914-
ax.boxplot([y1, y2], positions=[1, 2],
2915-
manage_xticks=False)
2914+
ax.boxplot([y1, y2], positions=[1, 2], manage_ticks=False)
29162915
new_xlim = ax.get_xlim()
29172916
assert_array_equal(old_xlim, new_xlim)
29182917

29192918

2919+
def test_boxplot_not_single():
2920+
fig, ax = plt.subplots()
2921+
ax.boxplot(np.random.rand(100), positions=[3])
2922+
ax.boxplot(np.random.rand(100), positions=[5])
2923+
fig.canvas.draw()
2924+
assert ax.get_xlim() == (2.5, 5.5)
2925+
assert list(ax.get_xticks()) == [3, 5]
2926+
assert [t.get_text() for t in ax.get_xticklabels()] == ["3", "5"]
2927+
2928+
29202929
def test_tick_space_size_0():
29212930
# allow font size to be zero, which affects ticks when there is
29222931
# no other text in the figure.

lib/matplotlib/ticker.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,7 @@ def __call__(self, x, pos=None):
335335

336336
class FixedFormatter(Formatter):
337337
"""
338-
Return fixed strings for tick labels based only on position, not
339-
value.
338+
Return fixed strings for tick labels based only on position, not value.
340339
"""
341340
def __init__(self, seq):
342341
"""

0 commit comments

Comments
 (0)