Skip to content
Merged
10 changes: 10 additions & 0 deletions doc/users/next_whats_new/errorbar_offsets.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Errorbar plots can shift which points have error bars
-----------------------------------------------------

Previously, `plt.errorbar()` accepted a kwarg `errorevery` such that the
command `plt.errorbar(x, y, yerr, errorevery=6)` would add error bars to
datapoints `x[::6], y[::6]`.

`errorbar()` now also accepts a tuple for `errorevery` such that
`plt.errorbar(x, y, yerr, errorevery=(start, N))` adds error bars to points
`x[start::N], y[start::N]`.
26 changes: 16 additions & 10 deletions examples/lines_bars_and_markers/errorbar_subsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,29 @@

# example data
x = np.arange(0.1, 4, 0.1)
y = np.exp(-x)
y1 = np.exp(-1.0 * x)
y2 = np.exp(-0.5 * x)

# example variable error bar values
yerr = 0.1 + 0.1 * np.sqrt(x)
y1err = 0.1 + 0.1 * np.sqrt(x)
y2err = 0.1 + 0.1 * np.sqrt(x/2)


# Now switch to a more OO interface to exercise more features.
fig, axs = plt.subplots(nrows=1, ncols=2, sharex=True)
ax = axs[0]
ax.errorbar(x, y, yerr=yerr)
ax.set_title('all errorbars')
fig, (ax_l, ax_c, ax_r) = plt.subplots(nrows=1, ncols=3,
sharex=True, figsize=(12, 6))

ax = axs[1]
ax.errorbar(x, y, yerr=yerr, errorevery=5)
ax.set_title('only every 5th errorbar')
ax_l.set_title('all errorbars')
ax_l.errorbar(x, y1, yerr=y1err)
ax_l.errorbar(x, y2, yerr=y2err)

ax_c.set_title('only every 6th errorbar')
ax_c.errorbar(x, y1, yerr=y1err, errorevery=6)
ax_c.errorbar(x, y2, yerr=y2err, errorevery=6)

fig.suptitle('Errorbar subsampling for better appearance')
ax_r.set_title('second series shifted by 3')
ax_r.errorbar(x, y1, yerr=y1err, errorevery=(0, 6))
ax_r.errorbar(x, y2, yerr=y2err, errorevery=(3, 6))

fig.suptitle('Errorbar subsampling for better appearance')
plt.show()
27 changes: 20 additions & 7 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3143,10 +3143,14 @@ def errorbar(self, x, y, yerr=None, xerr=None,
and *yerr*. To use limits with inverted axes, :meth:`set_xlim`
or :meth:`set_ylim` must be called before :meth:`errorbar`.

errorevery : positive integer, optional, default: 1
Subsamples the errorbars. e.g., if errorevery=5, errorbars for
every 5-th datapoint will be plotted. The data plot itself still
shows all data points.
errorevery : int or (int, int), optional, default: 1
draws error bars on a subset of the data. *errorevery* =N draws
error bars on the points (x[::N], y[::N]).
*errorevery* =(start, N) draws error bars on the points
(x[start::N], y[start::N]). e.g. errorevery=(6, 3)
adds error bars to the data at (x[6], x[9], x[12], x[15], ...).
Used to avoid overlapping error bars when two series share x-axis
values.

Returns
-------
Expand Down Expand Up @@ -3191,9 +3195,17 @@ def errorbar(self, x, y, yerr=None, xerr=None,
kwargs = {k: v for k, v in kwargs.items() if v is not None}
kwargs.setdefault('zorder', 2)

if errorevery < 1:
try:
offset, errorevery = errorevery
except TypeError:
offset = 0

if errorevery < 1 or int(errorevery) != errorevery:
raise ValueError(
'errorevery must be positive integer or tuple of integers')
if int(offset) != offset:
raise ValueError(
'errorevery has to be a strictly positive integer')
'errorevery\'s starting index must be an integer')

self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs)

Expand Down Expand Up @@ -3302,7 +3314,8 @@ def errorbar(self, x, y, yerr=None, xerr=None,
xlolims = np.broadcast_to(xlolims, len(x)).astype(bool)
xuplims = np.broadcast_to(xuplims, len(x)).astype(bool)

everymask = np.arange(len(x)) % errorevery == 0
everymask = np.zeros(len(x), bool)
everymask[offset::errorevery] = True

def xywhere(xs, ys, mask):
"""
Expand Down
23 changes: 23 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,29 @@ def test_errorbar_with_prop_cycle():
ax.errorbar(x=[2, 4, 10], y=[6, 4, 2], yerr=0.5)


@check_figures_equal()
def test_errorbar_offsets(fig_test, fig_ref):
x = np.linspace(0, 1, 15)
y = x * (1-x)
yerr = y/6

ax_ref = fig_ref.subplots()
ax_test = fig_test.subplots()

for color, shift in zip('rgbk', [0, 0, 2, 7]):
y += .02

# Using feature in question
ax_test.errorbar(x, y, yerr, errorevery=(shift, 4),
capsize=4, c=color)

# Using manual errorbars
# n.b. errorbar draws the main plot at z=2.1 by default
ax_ref.plot(x, y, c=color, zorder=2.1)
ax_ref.errorbar(x[shift::4], y[shift::4], yerr[shift::4],
capsize=4, c=color, fmt='none')


@image_comparison(baseline_images=['hist_stacked_stepfilled',
'hist_stacked_stepfilled'])
def test_hist_stacked_stepfilled():
Expand Down