7

I'd like to visualize a 20x20 matrix, where top left point is (-10, 9) and lower right point is (9, -10). So the x is increasing from left to right and y is decreasing from top to bottom. So my idea was to pass x labels as a list: [-10, -9 ... 9, 9] and y labels as [9, 8 ... -9, -10]. This worked as intended in seaborn (matplotlib), however doing so in plotly just reverses the image vertically. Here's the code:

import numpy as np
import plotly.express as px

img = np.arange(20**2).reshape((20, 20))
fig = px.imshow(img,
            x=list(range(-10, 10)),
            y=list(range(-10, 10)),
            )
fig.show()

enter image description here

import numpy as np
import plotly.express as px

img = np.arange(20**2).reshape((20, 20))
fig = px.imshow(img,
            x=list(range(-10, 10)),
            y=list(reversed(range(-10, 10))),
            )
fig.show()

enter image description here

Why is this happening and how can I fix it?

EDIT: Adding seaborn code to see the difference. As you can see, reversing the range for labels only changes the labels and has no effect on the image whatsoever, this is the effect I want in plotly.

import seaborn as sns
import numpy as np

img = np.arange(20**2).reshape((20, 20))

sns.heatmap(img, 
            xticklabels=list(range(-10, 10)),
            yticklabels=list(range(-10, 10))
            )

enter image description here

import seaborn as sns
import numpy as np

img = np.arange(20**2).reshape((20, 20))

sns.heatmap(img, 
            xticklabels=list(range(-10, 10)),
            yticklabels=list(reversed(range(-10, 10)))
            )

enter image description here

Ebrin
  • 179
  • 8
  • Could you please add the code of seaborn with the desired output? – Hamzah Dec 01 '22 at 08:07
  • The last figure is not correct, see how the last row where y=-10 is light orange color and does not match the colorscale. – Hamzah Dec 01 '22 at 11:38
  • @r-beginners Are you sure the link is correct? – Ebrin Dec 02 '22 at 11:08
  • @Hamzah I don't get your point, if the second to last figure is correct, then so is the last one. The only change I want to happen between those figures is the image should stay the same, but y axis labels should be reversed. And that's what's happening on the seaborn plots, but no on the plotly ones. – Ebrin Dec 02 '22 at 11:15
  • @r-beginners Your code indeed does what I've asked for. Please post your code as an answer. I'd also be interested in any insights regarding why my approach didn't work and why it was necessary to use the go.heatmap object. – Ebrin Dec 02 '22 at 15:07
  • Hey, did you see my answer below ? Just to be sure, because I don't get the scoring here.. I think the accepted answer, although it provides a working solution, fails to answer the question _Why is this happening and how can I fix it?_ and even misses the point about how the `autorange` (confusingly) works. Especially the fact that inverting the image `img.tolist()[::-1]` eliminates the problem of having to invert the labels : so no need to use go.heatmap() and no need to define all the ticks (unless by preference).. – EricLavault Dec 09 '22 at 20:44
  • @EricLavault Hey, I haven't logged onto this website in a while. You are indeed correct that your answer better matches my question, so I'm accepting your answer. Thanks for the thorough explanation! – Ebrin Dec 14 '22 at 22:57

3 Answers3

5

You should use origin :

origin (str, 'upper' or 'lower' (default 'upper')) – position of the [0, 0] pixel of the image array, in the upper left or lower left corner. The convention ‘upper’ is typically used for matrices and images.

import numpy as np
import plotly.express as px

img = np.arange(20**2).reshape((20, 20))
x = list(range(-10, 10))
y = list(reversed(range(-10, 10)))

fig = px.imshow(img, x=x, y=y, origin='lower')
fig.show()

Why reversing the labels doesn't work as expected ?

For single-channel arrays (2d, not rgb), px.imshow() builds a heatmap trace with by default the parameter autorange='reversed' on the yaxis, as "the convention ‘upper’ is typically used for matrices". This takes form with the following line :

autorange = True if origin == "lower" else "reversed"

The thing is that the autorange feature forces the orientation of the axis : when "reversed", the y's should be increasing from top to bottom (increasing vs decreasing inferred according to input data, not the same behavior with non-numeric strings), which means the yaxis is flipped iff the range of y's is not already reversed, in which case the whole is flipped vertically (any point (x, y) keeps its value). This is what you started with.

Now, if you reverse the labels in y, it actually reverses the yaxis coordinates against the data (values should decrease as y increases), but by doing this, since the y range is now already inverted, the autorange doesn't have to flip the image, so it results in the yaxis being reversed (expected) and the image being not flipped, hence compared to what you started with, the image goes from flipped to unflipped (unexpected).


Alternatives

In this situation, to avoid any confusion, an alternative to the solution above would be to define a specific range :

fig = px.imshow(img, x=x, y=y)
fig.update_yaxes(autorange=False, range=[-10.5, 9.5])

Or to have the data reoriented beforehand, in which case there is no need to reverse the y's (and even less to specify tickvals/ticktext, unless by preference) :

img = img.tolist()[::-1]    # reverse data
x = list(range(-10, 10))
y = list(range(-10, 10))    # instead of y

fig = px.imshow(img, x=x, y=y, origin='lower')

Output (same for the 3 code snippets)

heatmap screenshot

EricLavault
  • 12,130
  • 3
  • 23
  • 45
1

As I mentioned in my comment, the internal representation of px.imshow() is a heatmap. I coded your objective with a heatmap in a graph object. I didn't change this for any clear reason, I just took a different approach because I couldn't achieve it with px.imshow().

import plotly.graph_objects as go

img = np.arange(20**2).reshape((20, 20))

fig = go.Figure(data=go.Heatmap(z=img.tolist()[::-1]))

fig.update_yaxes(tickvals=np.arange(0, 20, 1), ticktext=[str(x) for x in np.arange(-10, 10, 1)])
fig.update_xaxes(tickvals=np.arange(0, 20, 1), ticktext=[str(x) for x in np.arange(-10, 10, 1)])
fig.update_layout(autosize=False, height=500, width=500)
fig.show()

enter image description here

r-beginners
  • 31,170
  • 3
  • 14
  • 32
-1
import plotly.graph_objects as go
import plotly.express as px

img = np.arange(20**2).reshape((20, 20))
fig = px.imshow(img,
    x=list(range(-10, 10)),
    y=list(range(10, 30, 1)),
)

fig.show()

Output:

enter image description here

Udesh
  • 2,415
  • 2
  • 22
  • 32
  • 1
    You just copied the code I already have in my post, lol. I want the top left coordinate to be (-10, 9), in your case it's (-10, -10). – Ebrin Nov 30 '22 at 19:56
  • @Ebrin, I think the top left is (-10, 10) and it is not as you said (-10,-10)? – Hamzah Dec 01 '22 at 08:01
  • @Hamzah the post's been edited, but right now the y is increasing from 10 to 30, which is not my desired result. – Ebrin Dec 01 '22 at 10:19