Gabriel
Gabriel

Reputation: 42329

Matplotlib custom colormap doesn't respect coloring

(Old version of the question is below)

I'm defining the following custom colormap where I assign the color white (#ffffff) to the float zero_pt.

import matplotlib.colors as col
from matplotlib.colors import LinearSegmentedColormap

zero_pt = 0.98181818181818181

cmap = LinearSegmentedColormap.from_list(
    'mycmap', [(0, 'blue'), (zero_pt, 'white'), (1, 'red')])

But when I try to retrieve the hex color code from the same zero_pt value, I don't get #ffffff (i.e.: white) as I would expect:

rgb = cmap(zero_pt)[:3]
print(col.rgb2hex(rgb))
> u'#ffdcdc'

Why is this and how can I get around this issue?




Old question here

I need to define a custom colormap where negative values are displayed in blue, zero values in white, and positive values in red. The code is below, and the necessary data files here (data.pkl) and here (edges.pkl).

import pickle
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap


with open('data.pkl', 'r') as f:
    H = pickle.load(f)
with open('edges.pkl', 'r') as f:
    edges = pickle.load(f)

# Define zero point for empty bins which should be colored in white.
zero_pt = 1. - H.max() / (H.max() - H.min())

print(H.min(), H.max(), zero_pt)

cmap = LinearSegmentedColormap.from_list(
    'mycmap', [(0, 'blue'), (zero_pt, 'white'), (1, 'red')])

Y, X = np.meshgrid(edges[0], edges[1])
plt.pcolormesh(X, Y, H, cmap=cmap, vmin=H.min(), vmax=H.max())

plt.show()

This results in

enter image description here

If we inspect the H array, we see that there are a lot of zero values that should be displayed in white, that instead show in pink in the figure above.

This doesn't happen will all the data I use, it just happens sporadically. I presume the issue has something to do with rounding? Is there a way to force cases such as this one to show zero values in white as they should?

Upvotes: 2

Views: 1756

Answers (1)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339052

As has been said in the comments you need to round the zero point value to a multiple of 1/N if N is the number of colors in the colormap. Of course the difference between the zero point value and any other colors in the colormap must be bigger than 1/N.

So here is a solution

import matplotlib.colors as col
from matplotlib.colors import LinearSegmentedColormap
import numpy as np 

N=256
zero_pt0 = 0.98181818181818181
zero_pt_rounded = np.ceil(zero_pt0*(N-1))/float(N-1)
print (zero_pt_rounded)

cmap = LinearSegmentedColormap.from_list(
    'mycmap', [(0, 'blue'), (zero_pt_rounded, 'white'), (1, 'red')], N=N)

# verify if original point zero_pt0 gives white color
rgb = cmap(zero_pt0)[:3]
print(col.rgb2hex(rgb))

Concerning your older question: It's not clear what the line zero_pt = 1. - H.max() / (H.max() - H.min()) should do. I would guess that you need to put

data_value_for_white color = ...
zero_pt = (data_value_for_white color - H.min()) / (H.max() - H.min())

Furthermore, in order to make sure the desired value is covered by the white color in the colormap, we would need to make the two adjacent colors in the colormap white.

N = 256 # number of colors in the colormap
zero_pt0 = np.floor(zero_pt*(N-1))/float(N-1)
zero_pt1 = np.ceil(zero_pt*(N-1))/float(N-1)
cmap = LinearSegmentedColormap.from_list('mycmap',
         [(0, 'blue'), (zero_pt0, 'white'),(zero_pt1, 'white'), (1, 'red')], N=N)

A complete solution would therefore look like:

import pickle
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, rgb2hex


with open('data.pkl', 'r') as f:
    H = pickle.load(f)
with open('edges.pkl', 'r') as f:
    edges = pickle.load(f)

# Define zero point for empty bins which should be colored in white.
data_value_for_white_color = 0.
zero_pt = (data_value_for_white_color - H.min()) / float(H.max() - H.min())

N=256
zero_pt0 = np.floor(zero_pt*(N-1))/float(N-1)
zero_pt1 = np.ceil(zero_pt*(N-1))/float(N-1)

cmap = LinearSegmentedColormap.from_list('mycmap',
     [(0, 'blue'), (zero_pt0, 'white'),(zero_pt1, 'white'), (1, 'red')], N=N)

Y, X = np.meshgrid(edges[0], edges[1])
plt.pcolormesh(X, Y, H, cmap=cmap, vmin=H.min(), vmax=H.max())
plt.show()

enter image description here

Upvotes: 1

Related Questions