Plotly imshow reversing y labels reverses the image

Question:

I’d like to visualize a 20×20 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

Asked By: Ebrin

||

Answers:

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

Answered By: devp

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

Answered By: r-beginners

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

Answered By: EricLavault
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.