cmap not behaving like it should although the values of the array are all in the cmap indices

Question:

MRE:

import numpy as np
import matplotlib.pyplot as plt

# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)

# Function to display the legend (for the masks images)
def show_legend(ax):
    patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
    ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)

arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)

plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())

The image I get is:
enter image description here

From my understanding, I don’t need to use norm because the mask values (my array values) are already within the range of the colormap indices.

However, I should get purple and green.

My intuition is that because there is no other values than 4 and 5, it maps the 4 to 0? But I don’t understand why it would work like that? And how to fix it easily?

Answers:

Matplotlib is using the range of colours you give to cover the range of numbers it sees

You can verify this explanation by putting in more gradations of values into your numerical array:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors 
import matplotlib.patches as mpatches 

# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)

# Function to display the legend (for the masks images)
def show_legend(ax):
    patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
    ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)

arr = np.array([[4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)

plt.imshow(arr, cmap=cmap)
show_legend(plt.gca())

The result is:

enter image description here

You do need to set the normalization

But use the vmin and vmax, rather than norm, to tell Matplotlib what range of values you want it to associate with the list of colours you have given.

Otherwise, it has no way of knowing that you want the list of colours you have presented to map to 1,2,3 etc. For example, how does it know you did not want them to be 0, 1, 2 etc, which might be more Pythonic?

Rather than having an arbitrary fixed default assumption, its default is just to use the the range of numerical values it actually sees.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors 
import matplotlib.patches as mpatches 

# Define a custom colormap (for the masks images)
colors = ['black', 'red', 'blue', 'purple', 'green']
labels = ['a', 'b', 'c', 'd', 'e']
cmap = mcolors.ListedColormap(colors)

# Function to display the legend (for the masks images)
def show_legend(ax):
    patches = [mpatches.Patch(color=colors[i], label=labels[i]) for i in range(len(labels))]
    ax.legend(handles=patches, bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0.)

arr = np.array([[4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 4., 4., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [4., 4., 4., 4., 4., 4., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.],
                [5., 5., 4., 4., 5., 5., 5., 5., 5., 5.]]
)

plt.imshow(arr, cmap=cmap, vmin=1.0, vmax=5.0)
show_legend(plt.gca())

The result is what you wanted:

enter image description here

Answered By: ProfDFrancis
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.