From fee159131c4ef28ee9418664326b62274d8770a4 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Fri, 19 Oct 2018 18:37:49 -0400 Subject: [PATCH 01/11] Added ability to offset errorbars with errorevery In addition to adding errorbars on a subset of data points one may add them to (2, 5, 8...) rather than (0, 3, 6...) to avoid overlaps. --- lib/matplotlib/axes/_axes.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c7ad1a61fc8d..1cbc417c2a80 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -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[3], x[9], x[15], x[21], ...). + Used to avoid overlapping error bars when two series share x-axis + values. Returns ------- @@ -3191,9 +3195,16 @@ 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: - raise ValueError( - 'errorevery has to be a strictly positive integer') + try: + offset, errorevery = errorevery + except TypeError: + offset = 0 + + int_msg = 'errorevery must be positive integer or tuple of integers' + if errorevery < 1 or int(errorevery) != errorevery: + raise ValueError(int_msg) + if int(offset) != offset: + raise ValueError(int_msg) self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) @@ -3302,7 +3313,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): """ From 0741bc701542c48a8f5b6bad376f88b9527fe0e4 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Fri, 19 Oct 2018 18:39:30 -0400 Subject: [PATCH 02/11] modified errorbar_subsample example to demonstrate feature --- .../errorbar_subsample.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/examples/lines_bars_and_markers/errorbar_subsample.py b/examples/lines_bars_and_markers/errorbar_subsample.py index 8b4d11087ac7..406cf51448e4 100644 --- a/examples/lines_bars_and_markers/errorbar_subsample.py +++ b/examples/lines_bars_and_markers/errorbar_subsample.py @@ -12,22 +12,29 @@ # example data x = np.arange(0.1, 4, 0.1) -y = np.exp(-x) +y = np.exp(np.vstack([-1.0 * x, -.5 * x])) # example variable error bar values -yerr = 0.1 + 0.1 * np.sqrt(x) +yerr = 0.1 + 0.1 * np.sqrt(np.vstack([x, x/2])) # Now switch to a more OO interface to exercise more features. -fig, axs = plt.subplots(nrows=1, ncols=2, sharex=True) +fig, axs = plt.subplots(nrows=1, ncols=3, sharex=True, figsize=(12, 6)) ax = axs[0] -ax.errorbar(x, y, yerr=yerr) +for i in range(2): + ax.errorbar(x, y[i], yerr=yerr[i]) + ax.set_title('all errorbars') ax = axs[1] -ax.errorbar(x, y, yerr=yerr, errorevery=5) -ax.set_title('only every 5th errorbar') - +for i in range(2): + ax.errorbar(x, y[i], yerr=yerr[i], errorevery=6) +ax.set_title('only every 6th errorbar') + +ax = axs[2] +for i in range(2): + ax.errorbar(x, y[i], yerr=yerr[i], errorevery=(3 * i, 6)) +ax.set_title('second series shifted by 3') fig.suptitle('Errorbar subsampling for better appearance') From 5394e84ee8d8dcde1a9c7ddd1a423aa3a04f10d8 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Fri, 19 Oct 2018 18:40:15 -0400 Subject: [PATCH 03/11] tests to check functionality using check_figures_equal decorator --- lib/matplotlib/tests/test_axes.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9f8e2ce60e24..a7234eddd069 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2945,6 +2945,30 @@ 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(): From d75fb49bc2d469eea726bae6a502f7de5c2347d9 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Fri, 19 Oct 2018 18:41:14 -0400 Subject: [PATCH 04/11] document new features --- doc/users/whats_new/errorbar_offsets.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 doc/users/whats_new/errorbar_offsets.rst diff --git a/doc/users/whats_new/errorbar_offsets.rst b/doc/users/whats_new/errorbar_offsets.rst new file mode 100644 index 000000000000..860eac785933 --- /dev/null +++ b/doc/users/whats_new/errorbar_offsets.rst @@ -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]`. From 59db2edd90e7bb1c028ea73c7f3dd42cfe0ec4b5 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 22 Oct 2018 12:08:13 -0400 Subject: [PATCH 05/11] style compliance fixes --- lib/matplotlib/tests/test_axes.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index a7234eddd069..ea45561d6599 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2954,16 +2954,15 @@ def test_errorbar_offsets(fig_test, fig_ref): 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 + # Using feature in question ax_test.errorbar(x, y, yerr, errorevery=(shift, 4), - capsize=4, c=color) + capsize=4, c=color) - #Using manual errorbars - #n.b. errorbar draws the main plot at z=2.1 by default + # 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') From 04d15d6ff437157e5232b00cc505a2b7475b353d Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 15 Apr 2019 20:37:34 -0400 Subject: [PATCH 06/11] fixed example to match behavior for two-tuple arguments --- lib/matplotlib/axes/_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 1cbc417c2a80..7fa88966421c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3147,8 +3147,8 @@ def errorbar(self, x, y, yerr=None, xerr=None, 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[3], x[9], x[15], x[21], ...). + (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. From 7b16d13affeeac2a15487ce385b03c0416ed2b19 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 22 Apr 2019 18:50:30 -0400 Subject: [PATCH 07/11] moved what's new to new what's new --- doc/users/{whats_new => next_whats_new}/errorbar_offsets.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/users/{whats_new => next_whats_new}/errorbar_offsets.rst (100%) diff --git a/doc/users/whats_new/errorbar_offsets.rst b/doc/users/next_whats_new/errorbar_offsets.rst similarity index 100% rename from doc/users/whats_new/errorbar_offsets.rst rename to doc/users/next_whats_new/errorbar_offsets.rst From f924d4d6ba9b3a9deb5028764c6014fc67e2c2e5 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 22 Apr 2019 18:52:07 -0400 Subject: [PATCH 08/11] proper length heading line for .rst --- doc/users/next_whats_new/errorbar_offsets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/next_whats_new/errorbar_offsets.rst b/doc/users/next_whats_new/errorbar_offsets.rst index 860eac785933..b2ca9f7a23c5 100644 --- a/doc/users/next_whats_new/errorbar_offsets.rst +++ b/doc/users/next_whats_new/errorbar_offsets.rst @@ -1,5 +1,5 @@ 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 From e24476eaaf140c9e951691fce4fba85d280bb0de Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 22 Apr 2019 19:22:11 -0400 Subject: [PATCH 09/11] unpacked and named y, yerr, and axs (previously iterables) for clarity. --- .../errorbar_subsample.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/examples/lines_bars_and_markers/errorbar_subsample.py b/examples/lines_bars_and_markers/errorbar_subsample.py index 406cf51448e4..eeebd5c11ee1 100644 --- a/examples/lines_bars_and_markers/errorbar_subsample.py +++ b/examples/lines_bars_and_markers/errorbar_subsample.py @@ -12,30 +12,29 @@ # example data x = np.arange(0.1, 4, 0.1) -y = np.exp(np.vstack([-1.0 * x, -.5 * 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(np.vstack([x, x/2])) +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=3, sharex=True, figsize=(12, 6)) -ax = axs[0] -for i in range(2): - ax.errorbar(x, y[i], yerr=yerr[i]) +fig, (ax_l, ax_c, ax_r) = plt.subplots(nrows=1, ncols=3, + sharex=True, figsize=(12, 6)) -ax.set_title('all errorbars') +ax_l.set_title('all errorbars') +ax_l.errorbar(x, y1, yerr=y1err) +ax_l.errorbar(x, y2, yerr=y2err) -ax = axs[1] -for i in range(2): - ax.errorbar(x, y[i], yerr=yerr[i], errorevery=6) -ax.set_title('only every 6th errorbar') +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) -ax = axs[2] -for i in range(2): - ax.errorbar(x, y[i], yerr=yerr[i], errorevery=(3 * i, 6)) -ax.set_title('second series shifted by 3') +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() From fc0f3b9162d5b3c8026e5c441767cfea1b4bcd9e Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Mon, 22 Apr 2019 20:59:49 -0400 Subject: [PATCH 10/11] moved error message into ValueError made 2-argument errorevery give a more accurate error message --- lib/matplotlib/axes/_axes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 7fa88966421c..8257bb4dcd70 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -3200,11 +3200,12 @@ def errorbar(self, x, y, yerr=None, xerr=None, except TypeError: offset = 0 - int_msg = 'errorevery must be positive integer or tuple of integers' if errorevery < 1 or int(errorevery) != errorevery: - raise ValueError(int_msg) + raise ValueError( + 'errorevery must be positive integer or tuple of integers') if int(offset) != offset: - raise ValueError(int_msg) + raise ValueError( + 'errorevery\'s starting index must be an integer') self._process_unit_info(xdata=x, ydata=y, kwargs=kwargs) From 2bfcf8cc8af9fb685dc214868b73c80f3110cf83 Mon Sep 17 00:00:00 2001 From: Joseph Albert <4261275+jcalbert@users.noreply.github.com> Date: Fri, 26 Apr 2019 17:09:31 -0400 Subject: [PATCH 11/11] tiny flake8 change --- examples/lines_bars_and_markers/errorbar_subsample.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lines_bars_and_markers/errorbar_subsample.py b/examples/lines_bars_and_markers/errorbar_subsample.py index eeebd5c11ee1..a54c66cf5655 100644 --- a/examples/lines_bars_and_markers/errorbar_subsample.py +++ b/examples/lines_bars_and_markers/errorbar_subsample.py @@ -21,7 +21,7 @@ # Now switch to a more OO interface to exercise more features. -fig, (ax_l, ax_c, ax_r) = plt.subplots(nrows=1, ncols=3, +fig, (ax_l, ax_c, ax_r) = plt.subplots(nrows=1, ncols=3, sharex=True, figsize=(12, 6)) ax_l.set_title('all errorbars')