11

How can I create a matplotlib colormap that maps 0 (and only 0) to white, and any other value 0 < v <= 1 to a smooth gradient such as rainbow?

It seems neither LinearSegmentedColormap nor ListedColormap can do this.

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
  • 1
    @tcaswell Indeed a duplicate, but the answer *here* is much better. `vmin = 0.001` is not good engouh. I have to always keep in mind what the minimum nonzero value might be in my matrix. – Szabolcs Aug 21 '14 at 21:10
  • Are you saying the relevant part of the answer here is the `0 + eps`? I marked them duplicate in chronological order, @JoeKington has the power to re-order them or appeal to meta if you have a major protest. (and I agree Joe's answer _is_ a better answer (and up-voted!)) – tacaswell Aug 22 '14 at 02:53
  • @tcaswell There's no protest at all, I just mentioned that I thought Joe's answer was more useful because it shows how to use `eps` (I didn't know) and it gives a solution for when `0` is in the middle. – Szabolcs Aug 22 '14 at 13:40
  • See the edit to my answer on the other question. Added a link back to this. – tacaswell Aug 22 '14 at 13:46
  • @tcaswell I didn't actually realize until now that the other answer was written by you. It's also a very useful, I didn't mean to say it wasn't. Thanks for adding the link! My point was that it was indeed useful for people who find your answer to also see the `eps` trick (in case they can't figure it out, like me). – Szabolcs Aug 22 '14 at 14:09

1 Answers1

20

There are a few ways of doing it. From your description of your values range, you just want to use cmap.set_under('white') and then set the vmin to 0 + eps.

For example:

import matplotlib.pyplot as plt
import numpy as np

cmap = plt.get_cmap('rainbow')
cmap.set_under('white')  # Color for values less than vmin

data = np.random.random((10, 10))
data[3:5, 7:] = 0

# Very small float such that 0.0 != 0 + eps
eps = np.spacing(0.0)

fig, ax = plt.subplots()
im = ax.imshow(data, interpolation='nearest', vmin=eps, cmap=cmap)
fig.colorbar(im, extend='min')
plt.show()

enter image description here

However, if 0 was in the middle of your data range, you could mask the values and set the color with cmap.set_bad (I'll use black as the color to distinguish from the default for masked portions.):

import matplotlib.pyplot as plt
import numpy as np

cmap = plt.get_cmap('rainbow')
cmap.set_bad('black')

data = np.random.normal(0, 1, (10, 10))
data[3:5, 7:] = 0

data = np.ma.masked_equal(data, 0)

fig, ax = plt.subplots()
im = ax.imshow(data, interpolation='nearest', cmap=cmap)
fig.colorbar(im)
plt.show()

enter image description here

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Did you mean set vmin to 0 + eps? Can you check my edit please? – Szabolcs Aug 21 '14 at 19:39
  • Yes, absolutely! (It wouldn't have worked with `vmin=0`.) Thanks for the catch! – Joe Kington Aug 21 '14 at 19:41
  • Marked this question as duplicate, you are revert my close vote and close the duplicates the other way if you want. – tacaswell Aug 22 '14 at 02:54
  • @tcaswell - I'm not worried about it one way or another. They're both good (albeit duplicate) answers. Let's just leave it with this one closed. – Joe Kington Aug 22 '14 at 22:19
  • I don't think this is really a duplicate since the question asks to replace the color for one of the levels. I will not reopen this question, but for people indeed seeking to use a different color *inside* the colormap, see e.g. [this question](https://stackoverflow.com/questions/48613920/use-of-extend-in-a-pcolormesh-plot-with-discrete-colorbar) on how to do it. – ImportanceOfBeingErnest Feb 05 '18 at 00:04