-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Make pcolor(mesh) preserve all data #9629
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
Pcolor and Pcolormesh now have *dropdata* kwarg and rcParam | ||
----------------------------------------------------------- | ||
|
||
Previously `.axes.Axes.pcolor` and `.axes.Axes.pcolormesh` handled | ||
the situation where *x* and *y* have the same (respective) size as *Z* by | ||
dropping the last row and column of *Z*, and *x* and *y* are regarded as the | ||
edges of the remaining rows and columns in *Z*. However, most users probably | ||
really want *x* and *y* centered on the rows and columns of *Z*, so if | ||
they specify *dropdata* as True, both methods will now linearly interpolate to | ||
get the edges of the bins, and *x* and *y* will specify the (linear) center of | ||
each gridcell in the pseudocolor plot. | ||
|
||
Users can also specify this by the new :rc:`pcolor.dropdata` in their | ||
``.matplotlibrc`` or via `.rcParams`. | ||
|
||
See :doc:`pcolormesh </gallery/images_contours_and_fields/pcolormesh_levels>` | ||
for an example. |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -5603,7 +5603,7 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, | |||||||
return im | ||||||||
|
||||||||
@staticmethod | ||||||||
def _pcolorargs(funcname, *args, allmatch=False): | ||||||||
def _pcolorargs(funcname, *args, allmatch=False, dropdata=True): | ||||||||
# If allmatch is True, then the incoming X, Y, C must have matching | ||||||||
# dimensions, taking into account that X and Y can be 1-D rather than | ||||||||
# 2-D. This perfect match is required for Gouraud shading. For flat | ||||||||
|
@@ -5665,14 +5665,34 @@ def _pcolorargs(funcname, *args, allmatch=False): | |||||||
raise TypeError('Dimensions of C %s are incompatible with' | ||||||||
' X (%d) and/or Y (%d); see help(%s)' % ( | ||||||||
C.shape, Nx, Ny, funcname)) | ||||||||
C = C[:Ny - 1, :Nx - 1] | ||||||||
|
||||||||
if dropdata: | ||||||||
C = C[:Ny - 1, :Nx - 1] | ||||||||
else: | ||||||||
def _interp_grid(X): | ||||||||
# helper for below | ||||||||
dX = np.diff(X, axis=1)/2. | ||||||||
X = np.hstack((X[:, [0]] - dX[:, [0]], | ||||||||
X[:, :-1] + dX, | ||||||||
X[:, [-1]] + dX[:, [-1]]) | ||||||||
) | ||||||||
return X | ||||||||
|
||||||||
if ncols == Nx: | ||||||||
X = _interp_grid(X) | ||||||||
Y = _interp_grid(Y) | ||||||||
|
||||||||
if nrows == Ny: | ||||||||
X = _interp_grid(X.T).T | ||||||||
Y = _interp_grid(Y.T).T | ||||||||
|
||||||||
C = cbook.safe_masked_invalid(C) | ||||||||
return X, Y, C | ||||||||
|
||||||||
@_preprocess_data() | ||||||||
@docstring.dedent_interpd | ||||||||
def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | ||||||||
vmax=None, **kwargs): | ||||||||
vmax=None, dropdata=None, **kwargs): | ||||||||
r""" | ||||||||
Create a pseudocolor plot with a non-regular rectangular grid. | ||||||||
|
||||||||
|
@@ -5686,7 +5706,9 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
|
||||||||
``pcolor()`` can be very slow for large arrays. In most | ||||||||
cases you should use the similar but much faster | ||||||||
`~.Axes.pcolormesh` instead. See there for a discussion of the | ||||||||
`~.Axes.pcolormesh` instead. See | ||||||||
:ref:`Differences between pcolor() and pcolormesh() | ||||||||
<differences-pcolor-pcolormesh>` for a discussion of the | ||||||||
differences. | ||||||||
|
||||||||
Parameters | ||||||||
|
@@ -5711,7 +5733,8 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
|
||||||||
The dimensions of *X* and *Y* should be one greater than those of | ||||||||
*C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in | ||||||||
which case the last row and column of *C* will be ignored. | ||||||||
which case the last row and column of *C* will be ignored if | ||||||||
*dropdata* is True (see below). | ||||||||
|
||||||||
If *X* and/or *Y* are 1-D arrays or column vectors they will be | ||||||||
expanded as needed into the appropriate 2-D arrays, making a | ||||||||
|
@@ -5751,6 +5774,13 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
snap : bool, default: False | ||||||||
Whether to snap the mesh to pixel boundaries. | ||||||||
|
||||||||
dropdata : bool, default: :rc:`pcolor.dropdata` | ||||||||
If True (default), and *X* and *Y* are the same size as C in their | ||||||||
respective dimensions, drop the last element of C in both | ||||||||
dimensions. If False, *X* and *Y* are assumed to be the midpoints | ||||||||
of the quadrilaterals, and their edges calculated by linear | ||||||||
interpolation. | ||||||||
|
||||||||
Returns | ||||||||
------- | ||||||||
collection : `matplotlib.collections.Collection` | ||||||||
|
@@ -5801,12 +5831,19 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
``pcolor()`` displays all columns of *C* if *X* and *Y* are not | ||||||||
specified, or if *X* and *Y* have one more column than *C*. | ||||||||
If *X* and *Y* have the same number of columns as *C* then the last | ||||||||
column of *C* is dropped. Similarly for the rows. | ||||||||
column of *C* is dropped if *dropdata* is True. Similarly for the rows. | ||||||||
If *dropdata* is False, then *X* and *Y* are assumed to be at the | ||||||||
middle of the quadrilaterals, and the edges of the quadrilaterals are | ||||||||
linearly interpolated. | ||||||||
|
||||||||
Note: This behavior is different from MATLAB's ``pcolor()``, which | ||||||||
This behavior is different from MATLAB's ``pcolor()``, which | ||||||||
always discards the last row and column of *C*. | ||||||||
""" | ||||||||
X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False) | ||||||||
if dropdata is None: | ||||||||
dropdata = rcParams['pcolor.dropdata'] | ||||||||
|
||||||||
X, Y, C = self._pcolorargs('pcolor', *args, dropdata=dropdata, | ||||||||
allmatch=False) | ||||||||
Ny, Nx = X.shape | ||||||||
|
||||||||
# unit conversion allows e.g. datetime objects as axis values | ||||||||
|
@@ -5903,7 +5940,8 @@ def pcolor(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
@_preprocess_data() | ||||||||
@docstring.dedent_interpd | ||||||||
def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | ||||||||
vmax=None, shading='flat', antialiased=False, **kwargs): | ||||||||
vmax=None, shading='flat', antialiased=False, | ||||||||
dropdata=None, **kwargs): | ||||||||
""" | ||||||||
Create a pseudocolor plot with a non-regular rectangular grid. | ||||||||
|
||||||||
|
@@ -5913,9 +5951,9 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
|
||||||||
*X* and *Y* can be used to specify the corners of the quadrilaterals. | ||||||||
|
||||||||
.. note:: | ||||||||
.. hint:: | ||||||||
|
||||||||
`~Axes.pcolormesh` is similar to `~Axes.pcolor`. It's much faster | ||||||||
`~Axes.pcolormesh` is similar to `~Axes.pcolor`. It is much faster | ||||||||
and preferred in most cases. For a detailed discussion on the | ||||||||
differences see :ref:`Differences between pcolor() and pcolormesh() | ||||||||
<differences-pcolor-pcolormesh>`. | ||||||||
|
@@ -5942,7 +5980,8 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
|
||||||||
The dimensions of *X* and *Y* should be one greater than those of | ||||||||
*C*. Alternatively, *X*, *Y* and *C* may have equal dimensions, in | ||||||||
which case the last row and column of *C* will be ignored. | ||||||||
which case the last row and column of *C* will be ignored, unless | ||||||||
*dropdata* is *False* (see below). | ||||||||
|
||||||||
If *X* and/or *Y* are 1-D arrays or column vectors they will be | ||||||||
expanded as needed into the appropriate 2-D arrays, making a | ||||||||
|
@@ -5991,6 +6030,13 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
snap : bool, default: False | ||||||||
Whether to snap the mesh to pixel boundaries. | ||||||||
|
||||||||
dropdata : bool, default: :rc:`pcolor.dropdata` | ||||||||
If True (default), and *X* and *Y* are the same size as C in their | ||||||||
respective dimensions, drop the last element of C in both | ||||||||
dimensions. If False, *X* and *Y* are assumed to be the midpoints | ||||||||
of the quadrilaterals, and their edges calculated by linear | ||||||||
interpolation. | ||||||||
|
||||||||
Returns | ||||||||
------- | ||||||||
mesh : `matplotlib.collections.QuadMesh` | ||||||||
|
@@ -6062,7 +6108,11 @@ def pcolormesh(self, *args, alpha=None, norm=None, cmap=None, vmin=None, | |||||||
|
||||||||
allmatch = (shading == 'gouraud') | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Also, I think the method definition for _pcolorargs needs to be updated to include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about a different kwarg than dropdata? Maybe something like xyvertices=False, xyedges. Something to insinuate that the current xy are grid points or vertices rather than edges. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I see what you mean, but not sure what to do about this. We don't want to imply that we will do anything if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem I see with xy_centers, xy_midpoints? If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not calculating the midpoint, I'm calculating the edges, assuming that the given co-ordinates are the midpoint, which I think is what 99.99% of users would expect. The fact is that I think this should be the default behaviour, but thats a whole back compatibility concern. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with what you've said (hence my There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the name should be changed. I disagree that this is what users want 99% of the time. Most of the time, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think 0.001% of users expect data to be dropped. As for imshow that is useless for unevenly spaced X or Y. |
||||||||
X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch) | ||||||||
if dropdata is None: | ||||||||
dropdata = rcParams['pcolor.dropdata'] | ||||||||
|
||||||||
X, Y, C = self._pcolorargs('pcolormesh', *args, | ||||||||
allmatch=allmatch, dropdata=dropdata) | ||||||||
Ny, Nx = X.shape | ||||||||
X = X.ravel() | ||||||||
Y = Y.ravel() | ||||||||
|
Uh oh!
There was an error while loading. Please reload this page.
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 changes I am suggesting take care of the case when X and/or Y are logarithmically spaced.
(e.g. visualizing a time series of fractional octave band sound pressure level data. See the following example)
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 like this logarithmic addition! The only thing I'd add is that you can still use the method and calculate dX's even if the points aren't evenly spaced, so the first if statement shouldn't be a requirement. It could just be the default after checking for the ratio.
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 don't think this will apply generally enough to be a good approach. If someone needs precision in the cell edges, they should calculate them themselves as they need, not count on matplotlib to do it. This is just a rough fix which is not quite as rough as just dropping the 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.
Yeah, there are a whole bunch of cases where this will fail/error out (
x = [10, 9, 8]
) that we could check for, but I'm not convinced its worth the extra code.Uh oh!
There was an error while loading. Please reload this page.
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 guess a possibility would be to do the grid interpolation in screen space? Something like... (assuming dropdata=False and the shapes are such that grid interp is necessary)
(very rough sketch)
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.
That seems way too fancy and hard to explain to the user. And I'm not even convinced it makes sense for curvilinear grids etc. Lets keep this simple and easy to explain. If the user wants something fancier they can calculate their own edges...
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.
This process could be converted into a helper function which could be showed off in a gallery example/matplotlib blog. If the OP is fine with it, I am interested in working on a PR demonstrating the process outlined here. Does this sound a like a good idea?