Skip to content

Commit 371b9ab

Browse files
authored
Merge pull request #18556 from QuLogic/errorevery
ENH: Accept same types to errorevery as markevery
2 parents 64950dd + fda57b0 commit 371b9ab

File tree

3 files changed

+90
-15
lines changed

3 files changed

+90
-15
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
``errorbar`` *errorevery* parameter matches *markevery*
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
Similar to the *markevery* parameter to `~.Axes.plot`, the *errorevery*
5+
parameter of `~.Axes.errorbar` now accept slices and NumPy fancy indexes (which
6+
must match the size of *x*).
7+
8+
.. plot::
9+
10+
x = np.linspace(0, 1, 15)
11+
y = x * (1-x)
12+
yerr = y/6
13+
14+
fig, ax = plt.subplots(2, constrained_layout=True)
15+
ax[0].errorbar(x, y, yerr, capsize=2)
16+
ax[0].set_title('errorevery unspecified')
17+
18+
ax[1].errorbar(x, y, yerr, capsize=2,
19+
errorevery=[False, True, True, False, True] * 3)
20+
ax[1].set_title('errorevery=[False, True, True, False, True] * 3')

lib/matplotlib/axes/_axes.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import itertools
33
import logging
44
import math
5-
from numbers import Number
5+
from numbers import Integral, Number
66

77
import numpy as np
88
from numpy import ma
@@ -3147,17 +3147,6 @@ def errorbar(self, x, y, yerr=None, xerr=None,
31473147
kwargs = {k: v for k, v in kwargs.items() if v is not None}
31483148
kwargs.setdefault('zorder', 2)
31493149

3150-
try:
3151-
offset, errorevery = errorevery
3152-
except TypeError:
3153-
offset = 0
3154-
3155-
if errorevery < 1 or int(errorevery) != errorevery:
3156-
raise ValueError(
3157-
'errorevery must be positive integer or tuple of integers')
3158-
if int(offset) != offset:
3159-
raise ValueError("errorevery's starting index must be an integer")
3160-
31613150
self._process_unit_info([("x", x), ("y", y)], kwargs, convert=False)
31623151

31633152
# Make sure all the args are iterable; use lists not arrays to preserve
@@ -3179,6 +3168,33 @@ def errorbar(self, x, y, yerr=None, xerr=None,
31793168
if not np.iterable(yerr):
31803169
yerr = [yerr] * len(y)
31813170

3171+
if isinstance(errorevery, Integral):
3172+
errorevery = (0, errorevery)
3173+
if isinstance(errorevery, tuple):
3174+
if (len(errorevery) == 2 and
3175+
isinstance(errorevery[0], Integral) and
3176+
isinstance(errorevery[1], Integral)):
3177+
errorevery = slice(errorevery[0], None, errorevery[1])
3178+
else:
3179+
raise ValueError(
3180+
f'errorevery={errorevery!r} is a not a tuple of two '
3181+
f'integers')
3182+
3183+
elif isinstance(errorevery, slice):
3184+
pass
3185+
3186+
elif not isinstance(errorevery, str) and np.iterable(errorevery):
3187+
# fancy indexing
3188+
try:
3189+
x[errorevery]
3190+
except (ValueError, IndexError) as err:
3191+
raise ValueError(
3192+
f"errorevery={errorevery!r} is iterable but not a valid "
3193+
f"NumPy fancy index to match 'xerr'/'yerr'") from err
3194+
else:
3195+
raise ValueError(
3196+
f"errorevery={errorevery!r} is not a recognized value")
3197+
31823198
label = kwargs.pop("label", None)
31833199
kwargs['label'] = '_nolegend_'
31843200

@@ -3219,6 +3235,7 @@ def errorbar(self, x, y, yerr=None, xerr=None,
32193235
base_style.pop('markerfacecolor', None)
32203236
base_style.pop('markeredgewidth', None)
32213237
base_style.pop('markeredgecolor', None)
3238+
base_style.pop('markevery', None)
32223239
base_style.pop('linestyle', None)
32233240

32243241
# Make the style dict for the line collections (the bars).
@@ -3260,7 +3277,7 @@ def errorbar(self, x, y, yerr=None, xerr=None,
32603277
xuplims = np.broadcast_to(xuplims, len(x)).astype(bool)
32613278

32623279
everymask = np.zeros(len(x), bool)
3263-
everymask[offset::errorevery] = True
3280+
everymask[errorevery] = True
32643281

32653282
def apply_mask(arrays, mask):
32663283
# Return, for each array in *arrays*, the elements for which *mask*

lib/matplotlib/tests/test_axes.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,8 +3373,25 @@ def test_errorbar_with_prop_cycle(fig_test, fig_ref):
33733373
ax.set_xlim(1, 11)
33743374

33753375

3376+
def test_errorbar_every_invalid():
3377+
x = np.linspace(0, 1, 15)
3378+
y = x * (1-x)
3379+
yerr = y/6
3380+
3381+
ax = plt.figure().subplots()
3382+
3383+
with pytest.raises(ValueError, match='not a tuple of two integers'):
3384+
ax.errorbar(x, y, yerr, errorevery=(1, 2, 3))
3385+
with pytest.raises(ValueError, match='not a tuple of two integers'):
3386+
ax.errorbar(x, y, yerr, errorevery=(1.3, 3))
3387+
with pytest.raises(ValueError, match='not a valid NumPy fancy index'):
3388+
ax.errorbar(x, y, yerr, errorevery=[False, True])
3389+
with pytest.raises(ValueError, match='not a recognized value'):
3390+
ax.errorbar(x, y, yerr, errorevery='foobar')
3391+
3392+
33763393
@check_figures_equal()
3377-
def test_errorbar_offsets(fig_test, fig_ref):
3394+
def test_errorbar_every(fig_test, fig_ref):
33783395
x = np.linspace(0, 1, 15)
33793396
y = x * (1-x)
33803397
yerr = y/6
@@ -3385,7 +3402,7 @@ def test_errorbar_offsets(fig_test, fig_ref):
33853402
for color, shift in zip('rgbk', [0, 0, 2, 7]):
33863403
y += .02
33873404

3388-
# Using feature in question
3405+
# Check errorevery using an explicit offset and step.
33893406
ax_test.errorbar(x, y, yerr, errorevery=(shift, 4),
33903407
capsize=4, c=color)
33913408

@@ -3395,6 +3412,27 @@ def test_errorbar_offsets(fig_test, fig_ref):
33953412
ax_ref.errorbar(x[shift::4], y[shift::4], yerr[shift::4],
33963413
capsize=4, c=color, fmt='none')
33973414

3415+
# Check that markevery is propagated to line, without affecting errorbars.
3416+
ax_test.errorbar(x, y + 0.1, yerr, markevery=(1, 4), capsize=4, fmt='o')
3417+
ax_ref.plot(x[1::4], y[1::4] + 0.1, 'o', zorder=2.1)
3418+
ax_ref.errorbar(x, y + 0.1, yerr, capsize=4, fmt='none')
3419+
3420+
# Check that passing a slice to markevery/errorevery works.
3421+
ax_test.errorbar(x, y + 0.2, yerr, errorevery=slice(2, None, 3),
3422+
markevery=slice(2, None, 3),
3423+
capsize=4, c='C0', fmt='o')
3424+
ax_ref.plot(x[2::3], y[2::3] + 0.2, 'o', c='C0', zorder=2.1)
3425+
ax_ref.errorbar(x[2::3], y[2::3] + 0.2, yerr[2::3],
3426+
capsize=4, c='C0', fmt='none')
3427+
3428+
# Check that passing an iterable to markevery/errorevery works.
3429+
ax_test.errorbar(x, y + 0.2, yerr, errorevery=[False, True, False] * 5,
3430+
markevery=[False, True, False] * 5,
3431+
capsize=4, c='C1', fmt='o')
3432+
ax_ref.plot(x[1::3], y[1::3] + 0.2, 'o', c='C1', zorder=2.1)
3433+
ax_ref.errorbar(x[1::3], y[1::3] + 0.2, yerr[1::3],
3434+
capsize=4, c='C1', fmt='none')
3435+
33983436

33993437
@image_comparison(['hist_stacked_stepfilled', 'hist_stacked_stepfilled'])
34003438
def test_hist_stacked_stepfilled():

0 commit comments

Comments
 (0)