Skip to content

Added ability to offset errorbars when using errorevery.#6280

Merged
timhoffm merged 11 commits into
matplotlib:masterfrom
jcalbert:errorbar-offset
Apr 28, 2019
Merged

Added ability to offset errorbars when using errorevery.#6280
timhoffm merged 11 commits into
matplotlib:masterfrom
jcalbert:errorbar-offset

Conversation

@jcalbert

@jcalbert jcalbert commented Apr 8, 2016

Copy link
Copy Markdown
Contributor

I was having a problem where errorbars for different series would perfectly overlap, as shown in the top plot:

compare

I added the ability to specify the errorevery such that

plt.errorbar(x,y,yerr, errorevery=7) still adds error bars to the 1st, 8th, 15th ... data points but

plt.errorbar(x,y,yerr, errorevery=(7,3)) adds error bars to the 3rd, 10th, 17th ... points.

I apologize if I'm violating some protocol - I haven't contributed to a project like this before.


P.S. The plot above was generated by:

from matplotlib import pyplot as plt
import numpy as np

x1 = np.arange(0,1000.)
x2 = np.arange(0,1000.)

y1 = np.cumsum(np.random.rand(x1.size)-.495)
y2 = np.cumsum(np.random.rand(x2.size)-.505)

ye1 = x1**.5 / 4
ye2 = x2**.5 / 4

dat1 = [x1,y1,ye1]
dat2 = [x2,y2,ye2]


fig = plt.figure(figsize=(12,8))

ax_before = fig.add_subplot(2,1,1); ax_before.set_title('Without offsets')
ax_after  = fig.add_subplot(2,1,2); ax_after.set_title('With offsets')

for x,y,ye in [dat1, dat2]:
    shift = np.all(y==y1) * 20

    ax_before.errorbar(x,y,ye, errorevery=40,capsize=5)
    ax_after.errorbar(x,y,ye, errorevery=(40,shift),capsize=5)

fig.savefig('compare.png')

@tacaswell tacaswell added this to the 2.1 (next point release) milestone Apr 8, 2016
Comment thread lib/matplotlib/axes/_axes.py Outdated
if not iterable(errorevery):
errorevery = (errorevery, 0)
try:
assert all(int(x)==x for x in errorevery)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I while ago we cleaned all of the asserts out of the main code base and use a if ... raise pattern.

@tacaswell

Copy link
Copy Markdown
Member

👍 I like the idea, just left some code-style comments.

Can you also add a test (just a png image comparison should be enough) and a whats_new entry ( https://github.com/matplotlib/matplotlib/tree/master/doc/users/whats_new )

@jcalbert

jcalbert commented Apr 8, 2016

Copy link
Copy Markdown
Contributor Author

Done - is there any documentation / examples I should do?

@tacaswell

Copy link
Copy Markdown
Member

👍 Looks good to me.

If you want to update the mark-every examples in the gallery that would be good.

Comment thread lib/matplotlib/axes/_axes.py Outdated
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.
every 5-th datapoint will be plotted. If errorevery=(a,b), points

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after comma in tuple.

@jcalbert

jcalbert commented Apr 9, 2016

Copy link
Copy Markdown
Contributor Author

In an earlier revision, I had validation of the sort which ensured

  1. errorevery==int(errorevery)
  2. offset==int(offset)
  3. errorevery>=1
  4. offset>=0
    but simplified it in accordance with @tacaswell 's suggestion above.

....

Regarding @QuLogic 's note:

Personally, I prefer
(np.arange(N) + offset) % errorevery == 0
over

offset %= errorevery 
np.arange(N) % errorevery == offset

@tacaswell

Copy link
Copy Markdown
Member

Sometimes I make bad suggestions 🐑

(np.arange(N) + offset) % errorevery == 0 seems like a nice way to do this.

@QuLogic

QuLogic commented Apr 11, 2016

Copy link
Copy Markdown
Member

That should be - offset.

@jcalbert

Copy link
Copy Markdown
Contributor Author

Hm, not sure how I broke test_savefig_to_stringio_eps_afm.

@jcalbert

Copy link
Copy Markdown
Contributor Author

(My bad, I didn't realize "Close and comment" closed the issue)

@tacaswell

Copy link
Copy Markdown
Member

That test is known to be flaky, but no one has been able to reproduce it reliably to debug it. We are pretty sure it is an artifact of running nose in parallel + not perfectly isolated tests.

@dstansby

dstansby commented Feb 6, 2017

Copy link
Copy Markdown
Member

This looks like a cool new feature! @jcalbert could you rebase this on to the current master branch, and then I'll have a look at it in detail?

@jcalbert

jcalbert commented Feb 7, 2017

Copy link
Copy Markdown
Contributor Author

@dstansby Alright, it's going to take me a second to learn git rebase though, since I haven't used it before.

@tacaswell

Copy link
Copy Markdown
Member

Sorry this got lost in the weeds 😞

@dstansby dstansby self-assigned this Feb 8, 2017
@tacaswell tacaswell reopened this Feb 24, 2019
@tacaswell tacaswell modified the milestones: v3.1.0, v3.2.0 Feb 24, 2019
@tacaswell

Copy link
Copy Markdown
Member

power-cycled to re-run CI.

This looks like a very useful feature (that got a lot of discussion), however from skimming the discussion it looks like there are still outstanding issues with the API being inconsistent with the API of plot?

@jcalbert

Copy link
Copy Markdown
Contributor Author

IIRC, I think it was getting mysteriously snagged on one of the CI tests but I don't know how to go back and check old results. I think we settled on having errorevery support a subset of the functionality of markevery, but that subset is consistent.

I'll rebase and fix one style issue "(6,3)" -> "(6, 3)" tomorrow

@jcalbert

Copy link
Copy Markdown
Contributor Author

Should be good now.

@tacaswell tacaswell added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label Apr 20, 2019
@@ -0,0 +1,10 @@
Errorbar plots can shift which points have error bars
----------------------

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decorator should be the same length as the title.

@tacaswell tacaswell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modulo moving the whats_new file to next_whats_new

@tacaswell

Copy link
Copy Markdown
Member

Thanks for sticking with this for so long!


# 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))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fig, axs = plt.subplots(nrows=1, ncols=3, sharex=True, figsize=(12, 6))
fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=3, sharex=True, figsize=(12, 6))

and using ax1, ax2 below is better than reassigning ax = ax[i] later.

# example data
x = np.arange(0.1, 4, 0.1)
y = np.exp(-x)
y = np.exp(np.vstack([-1.0 * x, -.5 * x]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, it's easier to follow if you keep the data in two separate variables:

y1 = np.exp(-x)
y2 = np.exp(-0.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]))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with y1err, y2err.

ax = axs[0]
ax.errorbar(x, y, yerr=yerr)
for i in range(2):
ax.errorbar(x, y[i], yerr=yerr[i])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With above changes simpler:

ax1.errorbar(x, y1, yerr=y1err)
ax1.errorbar(x, y2, yerr=y2err)

Same for the following for loops.

Comment thread lib/matplotlib/axes/_axes.py Outdated
except TypeError:
offset = 0

int_msg = 'errorevery must be positive integer or tuple of integers'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more readable to define the message directly in the context of the ValueError. Also, this allows to make the error message more specific.

        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 offset must be an integer >= 0')

@jcalbert

Copy link
Copy Markdown
Contributor Author

Added @timhoffm 's suggestions, fixed what's new.

@timhoffm

timhoffm commented Apr 26, 2019

Copy link
Copy Markdown
Member

flake8 complains:
./examples/lines_bars_and_markers/errorbar_subsample.py:24:57: W291 trailing whitespace

Doc build is broken due to #14003 (unrelated to this PR). Can be fixed by rebasing.

@timhoffm timhoffm merged commit 4507a59 into matplotlib:master Apr 28, 2019
@timhoffm

Copy link
Copy Markdown
Member

Thanks a lot for keeping at this! I think this is PR with the longest history I've merged into matplotlib. Anyway, glad that it's in 🎉. Hope to see you back some time.

@jcalbert jcalbert deleted the errorbar-offset branch May 3, 2019 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants