EStt - 6 months ago 76
Python Question

# Discrete then linear colormap in matplotlib

I have a matrix of data ranging from 0 to 102, and when using imshow i'd like to have 0 displayed as burnt red, 1 displayed as yellow-green and every other value from 2 to 102 in a linear gradient from standard green to dark green.

So far I've tried using the colormap 'Greens' and replacing the first two values but I has inconsistent results and I don't know how to use only half of the colormap.
I've also tried to generate my own colormap using a colordict and then overriding the first two entries but it also has inconsitent results depending on the data submitted (for example 1 displaying as burnt red)

You could use `make_colormap` to make a custom colormap:

``````mygreen = make_colormap([c('burnt red'),
c('yellow green'), idx[1], c('yellow green'),
(0,1,0), idx[2], (0,1,0),  # RGB (0,1,0) is standard green?
c('dark green')])
``````
• The argument to `make_colormap` is a sequence of RGB values and floats.

• Every float is sandwiched between two RGB values.

• The floats indicate locations (on a scale from 0.0 to 1.0) where the color map transitions from one RGB color to the next.

• The first and last values in the sequence are the first and last colors in the color map.

``````import numpy as np
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
np.random.seed(2016)

def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples.
- Every float is sandwiched between two RGB values.
- The floats indicate locations (on a scale from 0.0 to 1.0) where the
color map transitions from one RGB color to the next.
- The floats should be in increasing order
- The first and last values in the sequence are the first and last
colors in the color map.
http://stackoverflow.com/q/16834861/190597 (unutbu)
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)

# There are 103 integers from 0 to 102 (inclusive)
idx = np.linspace(0, 1, 103)
c = mcolors.ColorConverter().to_rgb
mygreen = make_colormap([c('burnt red'),
c('yellow green'), idx[1], c('yellow green'),
(0,1,0), idx[2], (0,1,0),  # RGB (0,1,0) is standard green?
c('dark green')])

arr = np.random.randint(0, 103, size=(11, 11))
print(np.where(arr==0))
print(np.where(arr==1))
plt.imshow(arr, interpolation='nearest', cmap=mygreen, vmin=0, vmax=102)
plt.colorbar(ticks=list(range(0, 100, 10))+[102])
plt.show()
``````

Another (probably better) option is to modify `arr` to take advantage of matplotlib's `cmap.set_under` and `cmap.set_over` methods. These colormap methods allow you to set a color for all values that go under or over specified limits:

``````cmap = make_colormap([(0,1,0), c('dark green')])
cmap.set_under('burnt red')
cmap.set_over('yellow green')
``````

Thus, in the special case where you have just two special colors, you could modify `arr` so 0 gets mapped to, say, a negative number, and 1 gets mapped to a number larger than 100, and shift the rest of the numbers to range from 0 to 100:

``````arr -= 2
arr = np.where(arr==-1, 102, arr)
``````

For example,

``````import numpy as np
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
np.random.seed(2016)

def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples.
- Every float is sandwiched between two RGB values.
- The floats indicate locations (on a scale from 0.0 to 1.0) where the
color map transitions from one RGB color to the next.
- The floats should be in increasing order
- The first and last values in the sequence are the first and last
colors in color map.
http://stackoverflow.com/q/16834861/190597 (unutbu)
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)

c = mcolors.ColorConverter().to_rgb
cmap = make_colormap([(0,1,0), c('dark green')])
cmap.set_under('burnt red')
cmap.set_over('yellow green')

arr = np.random.randint(0, 102, size=(11, 11))
# modify arr so that 0 is mapped to a negative number (-2) and 1 is mapped to a
# positive number greater than 100, (say, 102), and all other values are
# decreased by 2
arr -= 2
arr = np.where(arr==-1, 102, arr)

plt.imshow(arr, interpolation='nearest', cmap=cmap, vmin=0, vmax=100)
plt.colorbar(extend='both')
plt.show()
``````

Source (Stackoverflow)