0

I plotted a data with np.NaN. And I also want to change the center value of the colorbar due to the distribution of original data. But when I change the Vmin, Vmax and vcenter value of the colorbar, the color of np.NaN value changes to other colors other than white. So how can I fix that? Here follows the codes:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors_tmp
class MidpointNormalize(colors_tmp.Normalize):
    def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
        self.vcenter = vcenter
        colors_tmp.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

img = np.linspace(1,1000,1000).reshape((20,50))
img[(img>700)*(img<800)] = np.nan

fig, ax = plt.subplots(1,1)
sc = ax.imshow(img)

axpos = ax.get_position()
cbar_ax = fig.add_axes(
    [axpos.x1, axpos.y0, 0.01, axpos.height])  # l, b, w, h
cbar = fig.colorbar(sc, cax=cbar_ax)

enter image description here

Then I change the Vmin, Vmax and vcenter of the colorbar like this:

fig, ax = plt.subplots(1,1)
sc = ax.imshow(img)

axpos = ax.get_position()
cbar_ax = fig.add_axes(
    [axpos.x1, axpos.y0, 0.01, axpos.height])  # l, b, w, h
cbar = fig.colorbar(sc, cax=cbar_ax)
midnorm = MidpointNormalize(vmin=0, vcenter=200, vmax=500)
cbar.mappable.set_norm(midnorm)
cbar.mappable.set_cmap('BrBG')

The results are like below, we can see that the color of np.NaN is still white.

enter image description here

But when I change it to vmin=0, vcenter=800, vmax=1000, things get weird:

fig, ax = plt.subplots(1,1)
sc = ax.imshow(img)

axpos = ax.get_position()
cbar_ax = fig.add_axes(
    [axpos.x1, axpos.y0, 0.01, axpos.height])  # l, b, w, h
cbar = fig.colorbar(sc, cax=cbar_ax)
midnorm = MidpointNormalize(vmin=0, vcenter=800, vmax=1000)
cbar.mappable.set_norm(midnorm)
cbar.mappable.set_cmap('BrBG')

enter image description here

So why is that? and I want to keep the np.NaN value as white, I tried the ax.set_patch and also the set_bad(color="white"), they didn't work...so is there anyone who could help me? Thanks a lot!

Xu Shan
  • 175
  • 3
  • 11
  • Hi @JohanC, thanks for your comments! But I don't know why it was ```AttributeError: module 'matplotlib.colors' has no attribute 'TwoSlopeNorm'```... – Xu Shan May 28 '21 at 21:22
  • I tried the TwoSlopeNorm, or the divergingNorm in last version. It doesn't work... – Xu Shan May 28 '21 at 21:34
  • What exactly do you mean by "it does not work"? Does it crash? Does it hang? Does it give an unexpected result? ...? Note that the DivergingNorm has been deprecated since matplotlib 3.2. You might want to add some code to give `img` an exact initial value in order to create a reproducible example. – JohanC May 28 '21 at 21:57
  • I tried to reproduce your problem using the latest matplotlib version, but get the expected white band using your original code. Are you sure the complete band is `np.NaN`? Are you sure nothing else had been drawn on the same subplot? (I used `img = np.repeat(np.linspace(1, 1000, 18), 48).reshape(-1, 48); img[11:14,:] = np.nan` as test image). – JohanC May 28 '21 at 22:09
  • Hi @JohanC, thanks for your reply! I just found that I ignored the parts of original data...I have added it... – Xu Shan May 29 '21 at 20:16
  • Hi @JohanC, could you just share your scripts and figures by adding an answer? let me try your codes...thanks! And someone suggested me to use something like ```cmap = plt.get_cmap('BrBG').with_extremes(bad='white')```, but I found it didn't work...I didn't find the ```with_extremes``` in ```cbar.mappable.get_cmap()```. It was said that ```AttributeError: 'LinearSegmentedColormap' object has no attribute 'with_extremes'```. – Xu Shan May 29 '21 at 20:58
  • Well, `with_extremes()` is new in the latest matplotlib version. In older versions there is `cmap.set_bad(...)` – JohanC May 29 '21 at 21:02

1 Answers1

1

Using the latest matplotlib version (3.4.2), the code seems to work as expected.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors_tmp

class MidpointNormalize(Normalize):
    def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False):
        self.vcenter = vcenter
        colors_tmp.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

img = np.linspace(1, 1000, 1000).reshape((20, 50))
img[(img > 700) * (img < 800)] = np.nan

fig, ax = plt.subplots(1, 1)
sc = ax.imshow(img)

axpos = ax.get_position()
cbar_ax = fig.add_axes(
    [axpos.x1 + 0.01, axpos.y0, 0.01, axpos.height])  # l, b, w, h
cbar = fig.colorbar(sc, cax=cbar_ax)
midnorm = MidpointNormalize(vmin=0, vcenter=800, vmax=1000)
cbar.mappable.set_norm(midnorm)
cbar.mappable.set_cmap('BrBG')
plt.show()

imshow with nan

Additional, you could try:

  • to set the norm and the cmap directly when calling imshow
  • to use TwoSlopeNorm instead of a custom norm
  • to explicitly set the "bad" color (to either 'none' for transparent, showing the background, or 'white' to fix the color undependent of the background)
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors

img = np.linspace(1, 1000, 1000).reshape((20, 50))
img[(img > 700) & (img < 800)] = np.nan

fig, ax = plt.subplots(1, 1)
cmap = plt.get_cmap('BrBG')
cmap.set_bad('white')
midnorm = mcolors.TwoSlopeNorm(vmin=0, vcenter=800, vmax=1000)
sc = ax.imshow(img, norm=midnorm, cmap=cmap)

axpos = ax.get_position()
cbar_ax = fig.add_axes(
    [axpos.x1 + 0.01, axpos.y0, 0.01, axpos.height])  # l, b, w, h
cbar = fig.colorbar(sc, cax=cbar_ax)
plt.show()
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Thanks for your answer! I just ran your first case in my jupyternotebook, and I just get the same results like what I show in the question description...I checked that my Matplotlib is 3.1.3. I will upgrade it to 3.4.2 then get the new response to you then! Thanks again! – Xu Shan May 29 '21 at 21:24
  • Hi @JohanC, thanks for your help! And the upgrading helps...but one thing is that, if I save the figure to .eps file, why the np.nan is black in the eps figure...how could I fix that? – Xu Shan May 30 '21 at 12:42
  • The default "color" for NaNs would be transparent. So, you should explicitly set it to white if transparent doesn't work (so using `cmap.set_bad('white')`). – JohanC May 30 '21 at 12:47
  • Thanks for your reply! I also found a similar suggestion here: https://stackoverflow.com/a/2578873/10334846. But in that case, ```cmap.set_bad``` was called before the ```imshow``` while in my case I changed the cmap after the ```imshow```, then how should I do then? shall I just write ```cbar_cmap = cbar.mappable.get_cmap().copy()``` then ```cbar_cmap.set_bad('white')```? – Xu Shan May 30 '21 at 13:04
  • Well, the best thing to do is set the colormap during the call to `imshow` (as in my second example). Why do you want to change things afterwards? Anyway, if you want to change the image, taking a copy of the colormap certainly will not help. – JohanC May 30 '21 at 15:40
  • The reason that I have to change things afterwards is that I need to adjust the distribution of colorbar. For example if I have data of Pearson correlation coefficient which ranges from 0 to 1, while most of them are greater than 0.8. I need to see the difference of those points (>-0.8) or exaggerate the visualisation effects of those points. In that case, I need to use ``` [vmin, vcenter, vmax]=[0,0.8,1]``` – Xu Shan May 31 '21 at 18:23
  • You still can give a norm and a cmap to `imshow`. Even if you want to change it afterwards, you can already give a cmap with the correct "bad color" from the start. – JohanC May 31 '21 at 18:26