How to transform a 3D array correctly to a 2D array so that you can plot it properly?

Be informed, I am also using StandardScaler. I’m not sure if that affects anything. I can try to provide reproducible code for that, but perhaps it’s not necessary. It may just be a numpy thing.

import numpy as np

x = np.random.rand(60,60,1)

Now we plot this, to see the image.

cs = plt.imshow(x)
plt.colorbar()
plt.show()

60x60x1

We do this with imshow, which does accept 3D arrays. We need to use contourf, though, which only accepts 2D arrays.

x = x.reshape(1, 60*60)

Now, I want to plot this, so I want to get it into (60,60). But if I just reshape back to (60,60), the image is distorted.

x = x.reshape(60,60)
cs = plt.contourf(x)
plt.colorbar()
plt.show()

After reshaping to 60x60

I am specifically asking to reshape it so that 1. It was the same image as the first image, and 2. So that it could be done with contourf, since that this the only function for which a custom color bar is available AFAIK. One can see that the images are different because the max on each color bar is different (0.8 vs 1.05)

Answer

Your question touches on several different topics, it would help if it was more specific.

First, regarding the shape, I would always try to avoid reshaping yourself (manually), that’s only error prone and requires either hard coding the shape or retrieving it with something like x.shape etc.

In this case, you can simply drop the redundant “band” dimension by using:
x = np.squeeze(x)

That makes Numpy do the work, and preserves the other dimensions as they are.

Secondly, note that imshow and contourf have a different default origin. You can see this in your post because one image is vertically flipped compared to the other. This becomes obvious if you introduce some “artifact” in your random array. You can explicitly define the origin yourself if you want control over this.

Lastly, the colors for contourf are created by specifying a range (low, high) for the values in between to get a certain color. This is somewhat different compared to the more continuous mapping when using imshow. But you can pass a BoundaryNorm to imshow to make it behave similar to contourf.

Sample data:

x = np.random.rand(20, 20, 1)
x[2:8, 2:8] += 0.4
x = np.clip(x, 0, 1)
x = np.squeeze(x)

Colormap and Normalizer:

cmap = plt.cm.viridis
bounds = np.linspace(0, 1, 11)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

Plot result:

fig, (ax1, ax2) = plt.subplots(
    1,2, figsize=(10,5), constrained_layout=True, 
    subplot_kw=dict(xticks=[], yticks=[], aspect=1),
)

im = ax1.imshow(x, cmap=cmap, norm=norm, origin='upper')
cb = fig.colorbar(im, ax=ax1, orientation='horizontal', shrink=0.5, spacing='proportional')

cs = ax2.contourf(x, cmap=cmap, norm=norm, levels=bounds, origin='upper')
cb = fig.colorbar(cs, ax=ax2, orientation='horizontal', shrink=0.5, spacing='proportional')

The interpolation from contourf will “erode” the highs and lows a little, making it appear to have slightly less contrast/range. enter image description here