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

Skip to content

ENH: Catch masked array and invalid x, y to pcolormesh #9723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 13, 2017

Conversation

jklymak
Copy link
Member

@jklymak jklymak commented Nov 8, 2017

PR Summary

Docs clearly say that you can't pass masked arrays to pcolor and pcolormesh, yet we were inadvertently allowing it. This catches that error at the arg-check stage.

It also catches invalid numbers, though note that it won't catch invalid objects in ndarrays (i.e. if someone somehow puts a NaN in a datetime array).

If someone does pass a masked array to x or y, but it does not have any masked values, it will still work. I don't think this needs to be documented.

Addresses #9720

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant

@jklymak jklymak changed the title ENH: Catch masked array and invalid x, y to pcolor/mesh WIP: (hoops) ENH: Catch masked array and invalid x, y to pcolor/mesh Nov 8, 2017
@jklymak jklymak changed the title WIP: (hoops) ENH: Catch masked array and invalid x, y to pcolor/mesh WIP: (oops) ENH: Catch masked array and invalid x, y to pcolor/mesh Nov 8, 2017
@jklymak jklymak changed the title WIP: (oops) ENH: Catch masked array and invalid x, y to pcolor/mesh ENH: Catch masked array and invalid x, y to pcolor/mesh Nov 8, 2017
@jklymak jklymak added this to the v2.1.1 milestone Nov 8, 2017
@efiring
Copy link
Member

efiring commented Nov 8, 2017

Unlike pcolormesh, pcolor has documented support for masked X and/or Y; pcolor is implemented in a completely different way than pcolormesh, which makes this possible, though it may still be a bit silly.

@jklymak jklymak force-pushed the fixpcolorcopy branch 2 times, most recently from 88e76e8 to b6f8923 Compare November 10, 2017 22:27
Copy link
Member

@Kojoley Kojoley left a comment

Choose a reason for hiding this comment

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

Is not this check implementation in very costly?

Copy link
Member

@efiring efiring left a comment

Choose a reason for hiding this comment

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

I think this additional argument checking is reasonable, but I'm not 100% convinced yet.

for a in X, Y:
a = cbook.safe_masked_invalid(a)
if funcname == 'pcolormesh' and np.ma.is_masked(a):
raise TypeError(
Copy link
Member

Choose a reason for hiding this comment

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

I would use ValueError rather than TypeError; the problem is more a matter of the information in the container than the precise type of the container.

if funcname == 'pcolormesh':
# if it didn't fail the above it is masked but has no
# masked values, so strip the mask for pcolormesh
if type(X) is np.ma.core.MaskedArray:
Copy link
Member

Choose a reason for hiding this comment

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

I think all this can be simplified:

C = np.asanyarray(C)
X, Y = [cbook.safe_masked_invalid(x) for x in (X, Y)]
if funcname == 'pcolormesh':
    if np.ma.is_masked(X) or np.ma.is_masked(Y):
        raise ValueError("...")
    X, Y = X.data, Y.data

@@ -1190,6 +1190,13 @@ def test_pcolorargs():
ax.pcolormesh(x, y, Z[:-1, :-1], shading="gouraud")
with pytest.raises(TypeError):
ax.pcolormesh(X, Y, Z[:-1, :-1], shading="gouraud")
with pytest.raises(TypeError):
xx = x
Copy link
Member

Choose a reason for hiding this comment

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

Why are you renaming x? Do you mean to use xx = x[:]?

Copy link
Member Author

Choose a reason for hiding this comment

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

Umm well my understanding of scope and context management is rudimentary. If I change x in this with-block does it not change it for the next with-block?

Copy link
Member

Choose a reason for hiding this comment

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

If I change x in this with-block does it not change it for the next with-block?

It does. But it look suspicious, because xx = x makes xx alias of x, so it is a noop line.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh xx = x.copy()?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I don't need to rename this at all...

@efiring
Copy link
Member

efiring commented Nov 11, 2017

@Kojoley, the cost is in the checking and masking for nans:

In [1]: import numpy as np
In [2]: from matplotlib import cbook
In [4]: xx = np.random.randn(500, 1000)
In [5]: xx[xx < 0] = np.nan
In [6]: timeit cbook.safe_masked_invalid(xx)
1.36 ms ± 39.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

That means it is adding about 3 ms for handling the X and Y arrays in this case on a 5-year-old Macbook Pro. I think that is negligible--not that I like doing things that make mpl even a little slower.

xx[0] = np.NaN
ax.pcolormesh(xx, y, Z[:-1, :-1])
with pytest.raises(TypeError):
xx = np.ma.masked(x, mask=(x < 0))
Copy link
Member

Choose a reason for hiding this comment

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

Do this out of a context manager

@jklymak jklymak force-pushed the fixpcolorcopy branch 2 times, most recently from 0d4d3d5 to 9e1ba33 Compare November 11, 2017 22:10
@jklymak
Copy link
Member Author

jklymak commented Nov 11, 2017

Thanks @efiring and @Kojoley I think I caught most of your comments.

Copy link
Member

@efiring efiring left a comment

Choose a reason for hiding this comment

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

Getting close...

X, Y = [cbook.safe_masked_invalid(a) for a in args[:2]]
if funcname == 'pcolormesh':
if np.ma.is_masked(X) or np.ma.is_masked(Y):
raise TypeError(
Copy link
Member

Choose a reason for hiding this comment

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

Or ValueError?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oops, sorry, I changed it, and then accidentally changed it back

if type(X) is np.ma.core.MaskedArray:
X = X.data
if type(Y) is np.ma.core.MaskedArray:
Y = Y.data
Copy link
Member

Choose a reason for hiding this comment

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

The 4 lines above can be one line:

X, Y = X.data, Y.data

Copy link
Member Author

Choose a reason for hiding this comment

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

Not if they are ndarrays?

Copy link
Member

@efiring efiring Nov 11, 2017

Choose a reason for hiding this comment

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

I was incorrectly assuming that safe_masked_invalid always returned a masked array--and possibly it should do so for consistency, but that is another matter. Given that it doesn't, you are stuck with the checks, but please use if isinstance(X, np.ma.core.MaskedArray): which is simpler and more idiomatic.
It might be worth replacing your comment above these blocks with:

# safe_masked_invalid() returns an ndarray for dtypes other than floating point

@@ -1190,6 +1190,12 @@ def test_pcolorargs():
ax.pcolormesh(x, y, Z[:-1, :-1], shading="gouraud")
with pytest.raises(TypeError):
ax.pcolormesh(X, Y, Z[:-1, :-1], shading="gouraud")
x[0] = np.NaN
with pytest.raises(TypeError):
Copy link
Member

Choose a reason for hiding this comment

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

I see that TypeError is used in these earlier tests, so maybe you are using it for consistency. I think that in both cases ValueError would be more consistent with general mpl practice, and the way I think about such things. This is probably something where we need to state guidelines. @tacaswell, your thoughts? (I think that a long time ago I proposed we should define our own exception subclass of ValueError for use in argument validation, something like MPL_ArgumentError. Or maybe that idea never got further than my head.)

Change to just apply to pcolormesh, not pcolor
@efiring efiring changed the title ENH: Catch masked array and invalid x, y to pcolor/mesh ENH: Catch masked array and invalid x, y to pcolormesh Nov 13, 2017
@tacaswell tacaswell merged commit 1df45a4 into matplotlib:master Nov 13, 2017
dstansby added a commit that referenced this pull request Nov 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants