plotly.py icon indicating copy to clipboard operation
plotly.py copied to clipboard

Orientation of y-axis in Heatmap

Open hackermd opened this issue 9 years ago • 23 comments

The default orientation of the y-axis in the Heatmap graph is, at least to me, unexpected. The 0,0 coordinate of the plot is in the lower left corner. A heatmap is generally used to display a matrix, such as an image, however, and the 0,0 index into the matrix would be in the top left corner. An image is thus displayed flipped upside down. One can of course flip the z array or provide the values for y in reversed order, but this keeps the labelling of the axis and the display of y,x in the hover window unaffected. I like the way this is handled in matplotlib.pyplot.imshow, which provides an origin argument. An additional argument for the Heatmap graph to control the orientation of the y-axis would be nice.

hackermd avatar Feb 25 '16 06:02 hackermd

Figured it out. Python example:

plotly.graph_objs.Layout(
    yaxis=dict(autorange='reversed')
)

hackermd avatar Mar 03 '16 15:03 hackermd

I agree, while one can do yaxis=dict( autorange='reversed' ) this actually changes the handedness of the coordinate system you're working in. If the heatmap is not the final step, let's say you want to compare this against other data this easily becomes a nightmare. I would find it useful this flip of the origin to be made explicit.

elena-pascal avatar Feb 19 '19 15:02 elena-pascal

@elena-pascal did this ever get solved?

deliciouslytyped avatar Jun 15 '19 16:06 deliciouslytyped

@deliciouslytyped the issue had been closed a while back and I didn't reopen it just called into the void. Wasn't sure if it's affecting only me. Feel free to reopen if you feel strongly about it.

elena-pascal avatar Jun 15 '19 16:06 elena-pascal

I think I'd have to post a new issue.

deliciouslytyped avatar Jun 15 '19 16:06 deliciouslytyped

@elena-pascal I think something truly needs to be done, as I am in a point in a project when a lot of time got wasted just to find ways around.

Shivamshaiv avatar Mar 09 '20 16:03 Shivamshaiv

Hi @elena-pascal @Shivamshaiv @deliciouslytyped could you please explain why

yaxis=dict(
autorange='reversed'
)

is not a solution for you? Also note that px.imshow has an origin argument.

emmanuelle avatar Mar 09 '20 17:03 emmanuelle

The Plotly heatmap is intended to represent a discrete version of a scalar field, z= f(x, y), with x, y in a rectangle [a b] x [c, d]. All users participating in this discussion consider that a heatmap is generally used to display a matrix, such as an image, and pixel (0, 0) should be in the upper left corner. This is the case when using matplotlib. Matplotlib defines a heatmap as an image, but its imshow provides the possibility to set the origin as 'lower' or 'upper'. With origin='lower', you'll get the equivalent of the default plotly heatmap, while origin='upper' has the same effect as setting yaxis_autorange='reversed'

Generally people consider as being the "default definition" that given in a library they used before.

empet avatar Mar 09 '20 17:03 empet

Hi @elena-pascal @Shivamshaiv @deliciouslytyped could you please explain why

yaxis=dict(
autorange='reversed'
)

is not a solution for you? Also note that px.imshow has an origin argument.

It is the matrix representation aspect as described above. Simply reversing the axis messes up the handedness (I need to differentiate between front and back too) of the plotted matrix, and for a complicated geometry loosing this bit of sanity is undesirable.

elena-pascal avatar Mar 09 '20 23:03 elena-pascal

Thank you for your answer, but I don't understand the "handedness". Maybe a schematic drawing would help? Did you try passing your data to px.imshow? It should behave exactly like matplotlib's imshow with the same default for the origin.

emmanuelle avatar Mar 10 '20 13:03 emmanuelle

handedness

Most matrix maths implies right handed coordinate Cartesian systems (the left image). Flipping one axis is such a system results in having a left handed coordinate system (the right image). Moving from one to another is a pain as the definition of operations, eg rotation, must change. I know this can be a problem because we tried for years to compare different handedness images and left many scratching their heads.

Tbh, I don't quite remember now why I wanted to use heatmap and not imshow.

elena-pascal avatar Mar 11 '20 00:03 elena-pascal

Is there some reason this would be hard to implement? I don't remember the details - it's been a long time since I fought with this issue - but it should just be a simple coordinate transformation on the backend that if we try to fix from the calling side, ends up complicating things unnecessarily.

Since there's things like

plotly.graph_objs.Layout(
    yaxis=dict(autorange='reversed')
)

I see no reason why what we want shouldn't be.

TL;DR: I don't remember what the problem was, but I strongly believe it's valid and I don't see why it would be difficult to fix. And It should be fixed in the library. IIRC it's a major usability pain point if you need it.

deliciouslytyped avatar Mar 11 '20 02:03 deliciouslytyped

I'll just open a new issue pointing to this one.

deliciouslytyped avatar Mar 11 '20 02:03 deliciouslytyped

I'm really having trouble understanding the nature of the concern here with "handedness"... Here is what I see today with the default option and the reversed-axis option. The underlying data doesn't change, there's just one argument set to flip the coordinate system at display-time. If this isn't sufficient, what would be?

image

image

nicolaskruchten avatar Mar 11 '20 02:03 nicolaskruchten

@nicolaskruchten I'm not sure this will help, but add another gradient on the other axis so that it's not symmetric. You will see that there are two possible orientations (a flip across the diagonal). Presumably the result is reversed in the way that is not visible right now.

deliciouslytyped avatar Mar 11 '20 02:03 deliciouslytyped

Yes, the x and/or y axes can be flipped and in this case only the y axis was flipped.

nicolaskruchten avatar Mar 11 '20 02:03 nicolaskruchten

You can actually tell by looking at the tick labels :)

nicolaskruchten avatar Mar 11 '20 02:03 nicolaskruchten

Oh yeah. I think that might have actually been my original problem. :) (not sure though)

deliciouslytyped avatar Mar 11 '20 03:03 deliciouslytyped

Ok! “problem” in what sense? I’m struggling to see what is not possible today, give that all 4 possible combinations of x/y flip/no-flip are available :)

nicolaskruchten avatar Mar 11 '20 10:03 nicolaskruchten

I think what we're asking for is a matrix option for heatmap that defaults origin to top left. That would probably be the pythonic way.

The problem is that while heatmap is happy to take a numpy matrix as input, its shown origin is flipped with respect to the usual understanding of the y axis of a matrix. If the matrix has meaning in 3 dimensions than that also affects the z axis, or viewing direction. If a user is not careful and wants to look at matrices with histogram they will run into problems.

Let's consider a matrix: img = np.matrix([[10, 20, 60], [20, 10, 30], [30, 60, 10]])

Let's say I want to visualize how a 3D 90 degrees rotation around the normal to the image plane looks like for this matrix. If I use the standard definition of rotation then it should be something like this:

rotation

The rotation behaviour is going to be opposite than expected if I were to be more naive about orientations.

While inversing y axis achieves the correct image, and it could well be a solution for visualizing individual images, I don't think it is a good (sane) solution for matrix plotting with heatmap. The user is required to have a good understanding of the effect of axis flipping and why they have to flip y back. This can be tricky to be comfortable with since inversing axes in a matrix comes with more implications, eg affecting handedness, and I think this thread was proof that thinking about 3D orientations is a pain. For me personally, I know that seeing a flipped axis in the code will make me want to double check every time the sanity of the orientations.

summary: I would prefer to have a matrix plotting option that does not invert axes because axes inversion in linear algebra makes me very nervous.

elena-pascal avatar Mar 12 '20 00:03 elena-pascal

Thanks for the extra context @elena-pascal, I think I'm understanding.

A couple of points of context from the Plotly.py maintenance perspective:

  1. The meaning of "reversed" is with respect to an origin at the bottom left, which stems from the fact that the underlying layout engine for Plotly.py supports the plotting of multiple traces of different types on the same plot e.g. a heatmap and a scatter, and I suspect the scatter was implemented first.
  2. We cannot really change the defaults without making some possibly-large existing population of users unhappy, because whatever code they have today would unexpectedly result in upside-down heatmaps after an upgrade i.e. this would be a breaking change to our API. Notably, anyone using something like Datashader to overcome overplotting on scatterplots will have the same issue you are facing with respect to default behaviour.
  3. If we did change the defaults, the mechanism whereby we would do so is that we would set yaxis_autorange="reversed" on all plots containing only heatmap traces. This is actually what we do for plots containing only image traces. We made this choice basically because we agree with you! We were able to do this without triggering a "breaking change" when initially implementing image last fall because there was no pre-existing code relying on the other behaviour.
  4. Setting yaxis_autorange="reversed" is exactly what Plotly.js implicitly does under the hood for plots with only image traces and what px.imshow explicitly does under the hood to implement its default origin="upper" option, which works both with single-channel heatmap/matrix-like data and with multi-channel image-like data.

I guess the bottom-line good news is that we do in fact already have built-in functionality in Plotly to display matrix-like data the way you would expect, without forcing you to explicitly declare anything as "reversed": if you use px.imshow and leave the origin setting alone. The bad news is that if you inspect the figure (i.e. print(fig)), you will see a possibly-confusing autorange="reversed" in the y-axis, and there's no way we can change that without upsetting other people.

nicolaskruchten avatar Mar 12 '20 01:03 nicolaskruchten

Thank you for explaining the implementation implications and taking the time to listen to our concerns.

Yes, I do agree that changing the default would be a nightmare that would benefit way fewer people than those it would negatively affect. Under-the-hood implementation is all I hoped for: I imagined a matrix=True option could implement under the hood yaxis_autorange="reversed" and some sort of useful comment.

I'll remember to use imshow for matrix representation from now on.

elena-pascal avatar Mar 12 '20 02:03 elena-pascal

Taking into account that @elena-pascal considers that some users can have difficulties in undestanding why to get a heatmap as an image referenced to a left handed coordinate system we are performing an upside-down image flipping (i.e. yaxis_autorange='reversed', I suggest to give the following explanation in a plotly tutorials on heatmaps: If we denote by xOy the left handed coordinate system with origin O in the upper left corner of an image (array) of resolution (shape) m x n, and by XO'Y the right handed coordinate system with origin O in the lower left corner (the usual cartesian system oriented like in math, physics and default Plotly heatmap), then the pixel in the row i, and column j has the coordinates: x=j , y=i.

The relationship between the coordinates (x,y), and (X,Y) of a pixel is as follows:

X = x
Y = m-1-y

and conversely:

x = X
y = m-1-Y

But these relations point out that getting the coordinates (X Y) from (x,y) or reciprocally amounts to performing np.flipud(image).

empet avatar Mar 12 '20 15:03 empet