Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Adding 2d support to quadmesh set_array#16908

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
tacaswell merged 1 commit intomatplotlib:masterfromgreglucas:quadmesh_set_array
May 25, 2020

Conversation

greglucas
Copy link
Contributor

PR Summary

Adds the ability to callset_array() with 2d input arrays, which are what users typically make quadmesh's with.

importmatplotlib.pyplotaspltimportnumpyasnpz=np.random.random((5,5))fig,ax=plt.subplots()coll=ax.pcolormesh(np.ones(z.shape))coll.set_array(z)

It still callsravel() under the hood so that the private data is still 1-dimensional, it is just a convenience method for users. Relates to a portion of#15388.

PR Checklist

  • Has Pytest style unit tests
  • Code isFlake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

kaedonkers reacted with thumbs up emoji
def set_array(self, A):
# Allow QuadMesh.set_array(A) to accept 2d input
# as long is it is the same size as the current 1d data
if A.ndim == 2 and A.size == self._A.size:
Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It might make sense to actually issue a warning here ifA.size != self._A.size to address some common issues of the edges (n+1, m+1) compared to the data (n, m). Thoughts?

Copy link
Member

Choose a reason for hiding this comment

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

I'm a little confused as to why A needs to be raveled at all here? Why does QuadMesh store its array as 1-D?

@QuLogic
Copy link
Member

I think we need to be careful here to handle all the cases from#16258.

@QuLogicQuLogic requested a review fromjklymakMarch 26, 2020 22:52
Copy link
Member

@jklymakjklymak left a comment

Choose a reason for hiding this comment

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

I guess this needs to drop the last row and column if the old style co-ercion is to take place?

As noted below, I don't see whyA needs to be ravel-ed? Why don't we just storeA in place as an array?

@greglucas
Copy link
ContributorAuthor

I agree,@jklymak, I think that it would be ideal to store the actual data array.imshow already does this:
plt.imshow(np.random.random((5, 5))).get_array().ndim == 2

The current docstrings mentionsvertices (a 2d mesh) andcoordinates (n x 2) mesh, which doesn't really help the confusion here

classQuadMesh(Collection):
"""
Class for the efficient drawing of a quadrilateral mesh.
A quadrilateral mesh consists of a grid of vertices.
The dimensions of this array are (*meshWidth* + 1, *meshHeight* + 1).
Each vertex in the mesh has a different set of "mesh coordinates"
representing its position in the topology of the mesh.
For any values (*m*, *n*) such that 0 <= *m* <= *meshWidth*
and 0 <= *n* <= *meshHeight*, the vertices at mesh coordinates
(*m*, *n*), (*m*, *n* + 1), (*m* + 1, *n* + 1), and (*m* + 1, *n*)
form one of the quadrilaterals in the mesh. There are thus
(*meshWidth* * *meshHeight*) quadrilaterals in the mesh. The mesh
need not be regular and the polygons need not be convex.
A quadrilateral mesh is represented by a (2 x ((*meshWidth* + 1) *
(*meshHeight* + 1))) numpy array *coordinates*, where each row is
the *x* and *y* coordinates of one of the vertices. To define the
function that maps from a data point to its corresponding color,
use the :meth:`set_cmap` method. Each of these arrays is indexed in
row-major order by the mesh coordinates of the vertex (or the mesh
coordinates of the lower left vertex, in the case of the colors).
For example, the first entry in *coordinates* is the coordinates of the
vertex at mesh coordinates (0, 0), then the one at (0, 1), then at (0, 2)
.. (0, meshWidth), (1, 0), (1, 1), and so on.

The reason I chose this simplistic thing was that I didn't want to go down the rabbit hole of deprecation issues. Would it be possible to change that at this point, even? My guess is that people may be usingget_array() andset_array() with expected 1d arrays both ways, so I'm not sure how we would go about adding a 2d option without messing with someone's current 1d implementation.

Even more confusing through this all is that you canset_array() with a different shape than the coordinates or original data!
Try:
plt.imshow(np.random.random((5, 5))).set_array(np.random.random((5, 4)))
and look at where the ticks are.

andQuadMesh will actually tile your missing data for you:
plt.pcolormesh(np.arange(25).reshape((5, 5))).set_array(np.arange(10).reshape((5, 2)).ravel())

The two methods handle the different sized arrays differently. These seem like they could be quite confusing mistakes to track down!

After that long digression... I agree that this current implementation I have here is less than ideal and may lead to more confusion down the line. I would be willing to add 2d capabilities toQuadMesh if people think that is an OK idea. I also would be in favor of starting to add some requirements onset_array() depending on what is stored in the coordinates currently.

@greglucas
Copy link
ContributorAuthor

I looked a little more into this and the current backend implementors expect the shape of coordinates to be (m+1, n+1, 2), and facecolors/edgecolors to be (m*n). This is baked into the backends already to be flattened arrays and my guess is it would possibly cause a headache to change this.

I just pushed up a new proposed modification that will allowset_array() to accept 2D arrays for QuadMesh now and ravel whatever data they have (either 1D or 2D) into the correct shape internally to pass onto the backends. It maintains current functionality and just extends it to allow 2D data arrays for QuadMesh's.

There could be more checks done on the Python side of things to make sure arrays are the right size etc... But, that also wasn't done before so I'm not sure it should be done.

Copy link
Member

@jklymakjklymak left a comment

Choose a reason for hiding this comment

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

This seems an improvement to me. We can discuss doing something with more context later.

@tacaswelltacaswell added this to thev3.3.0 milestoneMar 30, 2020
@tacaswell
Copy link
Member

plt.imshow(np.random.random((5, 5))).set_array(np.random.random((5, 4))) ...

This is the expected behavior due to the way imshow works under the hood (it has both a data array and an extent seehttps://matplotlib.org/tutorials/intermediate/imshow_extent.html). The alternate behavior (the extent changing when you set the data) would be much more confusing.

Does fail loudly (if from a not great place) if you set miss-shapen data? Are there cases that used to fail that now pass?

greglucas reacted with thumbs up emoji

@@ -782,7 +782,8 @@ def update_scalarmappable(self):
"""Update colors from the scalar mappable array, if it is not None."""
if self._A is None:
return
if self._A.ndim > 1:
# QuadMesh can map 2d arrays
if self._A.ndim > 1 and not isinstance(self, QuadMesh):
Copy link
Member

Choose a reason for hiding this comment

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

Could we do the raveling here as

ifisinstance(self,QuadMesh):self._A=self._A.ravel()else:raiseValueError(...)

It keeps everything a bit more consistent shape wise?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

That's actually what I had originally, but force-pushed over. The downside to it is if someone calls get_array() it is a different shape returned, which could cause confusion?

Copy link
Member

Choose a reason for hiding this comment

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

That is a fair point, but I am also worried about the shape stability of people who have code written against QuadMesh who are now going to be suprised that sometimes they get back 2d data.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

That should only happen if they pass in 2d data, which was not possible before. So, I think all the before cases were 1d inputs and will return 1d inputs still. This is really for my selfish future motivation of wanting to call update animations without forgetting to ravel() and get the ValueError thrown my way. I completely agree though, it does add another layer of potential confusion and that should be weighed on pros/cons.

Copy link
Member

Choose a reason for hiding this comment

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

I am worried about the (hypothetical) that someone has written a function that takes in aQuadMesh, usesget_array(), and assumes 1d data. If we do the reshaping at the last minute then that assumption is no longer valid, but there is no way for the function author to reasonably know.

I do see both sides of this and neither is obviously better.

@tacaswell
Copy link
Member

I am 👍 on this in principle, have a small concern that this is going to mask other bugs.

@QuLogic
Copy link
Member

So do we need tests for the various other input shape combinations? I guess at least, it would be nice to have tests for invalid shapes.

@greglucas
Copy link
ContributorAuthor

Unfortunately, there aren't really any "bad" shapes now. Quadmesh will accept flattened arrays that are larger or smaller no problem and then either chop the data off or tile it up for you respectively. See this long comment for the background there:#16908 (comment)

I'm hesitant to add a strict check onA's shape/size incase someone is (ab)using that feature. I did have that initially for just the 2d Quadmesh case (see:#16908 (comment)) but I took that away to just delegate the getting/setting of A to the superclass instead.

@tacaswell
Copy link
Member

@greglucas we have a preference for rebasing rather than merging the master branch into feature branches. I took the liberty of doing the re-base and force pushed to your branch.

greglucas reacted with thumbs up emoji

@greglucas
Copy link
ContributorAuthor

Thanks! I also noticed that one of the comments was incorrect now too, so I pushed up a change for that just now.

@efiring
Copy link
Member

I think this is OK as a logical improvement, but perhaps the next step should be to override Quadmesh.set_array so that it checks that the dimensions of its argument, whether 1-D or 2-D, are consistent with the mesh dimensions set when the Quadmesh was instantiated.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@tacaswelltacaswelltacaswell approved these changes

@jklymakjklymakjklymak approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
v3.3.0
Development

Successfully merging this pull request may close these issues.

5 participants
@greglucas@QuLogic@tacaswell@efiring@jklymak

[8]ページ先頭

©2009-2025 Movatter.jp