-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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
Conversation
223e9a9
to
58eb8c2
Compare
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. |
88e76e8
to
b6f8923
Compare
There was a problem hiding this 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?
There was a problem hiding this 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.
lib/matplotlib/axes/_axes.py
Outdated
for a in X, Y: | ||
a = cbook.safe_masked_invalid(a) | ||
if funcname == 'pcolormesh' and np.ma.is_masked(a): | ||
raise TypeError( |
There was a problem hiding this comment.
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.
lib/matplotlib/axes/_axes.py
Outdated
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: |
There was a problem hiding this comment.
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
lib/matplotlib/tests/test_axes.py
Outdated
@@ -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 |
There was a problem hiding this comment.
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[:]
?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh xx = x.copy()
?
There was a problem hiding this comment.
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...
@Kojoley, the cost is in the checking and masking for nans:
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. |
lib/matplotlib/tests/test_axes.py
Outdated
xx[0] = np.NaN | ||
ax.pcolormesh(xx, y, Z[:-1, :-1]) | ||
with pytest.raises(TypeError): | ||
xx = np.ma.masked(x, mask=(x < 0)) |
There was a problem hiding this comment.
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
0d4d3d5
to
9e1ba33
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting close...
lib/matplotlib/axes/_axes.py
Outdated
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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or ValueError?
There was a problem hiding this comment.
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
lib/matplotlib/axes/_axes.py
Outdated
if type(X) is np.ma.core.MaskedArray: | ||
X = X.data | ||
if type(Y) is np.ma.core.MaskedArray: | ||
Y = Y.data |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
lib/matplotlib/tests/test_axes.py
Outdated
@@ -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): |
There was a problem hiding this comment.
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.)
9e1ba33
to
f308e0e
Compare
Change to just apply to pcolormesh, not pcolor
f308e0e
to
44d455a
Compare
Backport PR #9723 on branch v2.1.x
PR Summary
Docs clearly say that you can't pass masked arrays to
pcolor
andpcolormesh
, 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