I'm trying to do a heatmap in python with an intermediate color at the 50% percentile. I do it regularly with excel, but I can't get it working on my automated python code.
In (red,yellow,Green) you can see my excel version, in yellow and blue my python one.
Just to clarify, I don't mind it to be a degradation of two colors, I just want to give the same importance to the 50% top percentile as to the bottom.
my code simplyfied code is:
import pandas as pd
import seaborn as sns
data = {
'row1': [90,95,99,50,50,45,0],
'row2': [99,98,100,100,98,99,80],
'row3': [98,97,99,100,96,95,98],
'row4': [99,98,100,100,98,99,100]
}
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')
sns.heatmap(df.round(), annot=True,ax=ax, cmap="YlGnBu")
Thank you in advanced for any help!
It's usually not desireable to change the colormap itself. Rather, one would change the normalization of values to colors. To this end a midpoint normalization may be used. The clear advantage is that this concept will work for just any colormap and there is no need to create a custom one for each different median value in use.
Unfortunately, seaborn does not allow to use custom normalizations. But creating the heatmap with matplotlib itself is equally easy, as shown in the annotated_heatmap example.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.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.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y))
data = {
'row1': [90,95,99,50,50,45,0],
'row2': [99,98,100,100,98,99,80],
'row3': [98,97,99,100,96,95,98],
'row4': [99,98,100,100,98,99,100]
}
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')
norm = MidpointNormalize(midpoint=np.median(df.values))
im = ax.imshow(df.values, cmap="YlGnBu", norm=norm)
fig.colorbar(im)
# Loop over data dimensions and create text annotations.
textcolors = ["k" ,"w"]
threshold = 55
for i in range(len(df)):
for j in range(len(df.columns)):
text = ax.text(j, i, df.values[i, j],
ha="center", va="center",
color=textcolors[df.values[i, j] > threshold])
plt.show()
Following the link provided by #StefanS I came up with the following way of registering my own cmap using, in my case, the median:
median = df.median().median()/100.0
c_red_yl_ = {'red': ((0.0, 0.8, 0.8),
(median, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 0.0),
(median, 1.0, 1.0),
(1.0, 0.8, 0.8)),
'blue': ((0.0, 0.0, 0.0),
(median, 0.0, 0.0),
(1.0, 0.0, 0.0))
}
plt.register_cmap(name='custom', data=cdict1)
I hope it's useful for someone else.
You can do something like this:
import matplotlib as mpl
fig, ax = plt.subplots(figsize=(9, 4))
df = pd.DataFrame.from_dict(data,orient='index')
cmap1 = mpl.colors.ListedColormap(['y'])
sns.heatmap(df.round(), annot=True,ax=ax, cmap="YlGnBu")
sns.heatmap(df.round(), mask=df.round() > 50, cmap=cmap1, cbar=True)
plt.show()
Related
I wanted to plot many binary heatmaps. Most of them have both 0 and 1 entries but few of them have complete 0 or 1. In those cases the heatmaps are not showing correct. In the following example subplot 3 and 4 colormap is not correct. Is there anyway to select colormap automatically based on logical True/False value? Thanks.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib import gridspec
import numpy as np
import seaborn as snn
import random
M=np.zeros((2,5))
for j in range(2):
for i in range(5):
r=random.random()
M[j,i]=r
print(M)
#[[0.61060519 0.04500793 0.74199826 0.22084509 0.31493589]
#[0.3432519 0.59293327 0.50043671 0.07201856 0.65329049]]
M0=M>0.5
M1=M>0
M2=M>2
M0 = M0.astype(float)
M1 = M1.astype(float)
M2 = M2.astype(float)
fig=plt.figure(figsize=(10,7))
gs = fig.add_gridspec(ncols=1, nrows=4, height_ratios=[1,1, 1,1])
ax0=fig.add_subplot(gs[0])
ax1=fig.add_subplot(gs[1])
ax2=fig.add_subplot(gs[2])
ax3=fig.add_subplot(gs[3])
colors = ((1.0, 1.0, 0.0), (1, 0.0, 1.0))
cmap = LinearSegmentedColormap.from_list('Custom', colors, len(colors))
b=snn.heatmap(M,ax=ax0)
b=snn.heatmap(M0,cmap=cmap,ax=ax1)
colorbar = b.collections[0].colorbar
colorbar.set_ticks([0.25,0.75])
colorbar.set_ticklabels(['0', '1'])
b=snn.heatmap(M1,cmap=cmap,ax=ax2)
colorbar = b.collections[0].colorbar
colorbar.set_ticks([0.25,0.75])
colorbar.set_ticklabels(['0', '1'])
b=snn.heatmap(M2,cmap=cmap,ax=ax3)
colorbar = b.collections[0].colorbar
colorbar.set_ticks([0.25,0.75])
colorbar.set_ticklabels(['0', '1'])
fig.tight_layout()
fig.savefig('a.png',dpi=300)
plt.close()
I edited my code now and it seems it is working what I wanted. But you have better suggestion please let me know.
M0=M>0.5
M1=M>0
M2=M>2
M0 = M0.astype(float)
M1 = M1.astype(float)
M2 = M2.astype(float)
fig=plt.figure(figsize=(10,7))
gs = fig.add_gridspec(ncols=1, nrows=4, height_ratios=[1,1, 1,1])
ax0=fig.add_subplot(gs[0])
ax1=fig.add_subplot(gs[1])
ax2=fig.add_subplot(gs[2])
ax3=fig.add_subplot(gs[3])
def chooseColorBar(ax,M):
if np.mean(M)==0:
colors = ((1.0, 1.0, 0.0),(1.0, 1.0, 0.0))
cmap = LinearSegmentedColormap.from_list('Custom', colors, len(colors))
b=snn.heatmap(M,cmap=cmap,ax=ax)
colorbar=b.collections[0].colorbar
colorbar.set_ticks([0])
colorbar.set_ticklabels(['0'])
elif np.mean(M)==1:
colors = ((1, 0.0, 1.0),(1, 0.0, 1.0))
cmap = LinearSegmentedColormap.from_list('Custom', colors, len(colors))
b=snn.heatmap(M,cmap=cmap,ax=ax)
colorbar=b.collections[0].colorbar
colorbar.set_ticks([1])
colorbar.set_ticklabels(['1'])
else:
colors = ((1.0, 1.0, 0.0), (1, 0.0, 1.0))
cmap = LinearSegmentedColormap.from_list('Custom', colors, len(colors))
b=snn.heatmap(M,cmap=cmap,ax=ax)
colorbar=b.collections[0].colorbar
colorbar.set_ticks([0.25,0.75])
colorbar.set_ticklabels(['0', '1'])
b=snn.heatmap(M,ax=ax0)
chooseColorBar(ax1,M0)
chooseColorBar(ax2,M1)
chooseColorBar(ax3,M2)
fig.tight_layout()
fig.savefig('a.png',dpi=300)
plt.close()
It looks like seaborn doesn't like boolean entries. When you make M1 > 0, etc., that gives you a boolean array. When are the entries are True or False, there is some problems. You can convert it to float and it will work. So, after you create M0/1/2, add these lines..
M0 = M0.astype(float)
M1 = M1.astype(float)
M2 = M2.astype(float)
This will give you the right plot. Tried with all True, all False, etc. Appears to be working fine.
I was trying to create custom color map with exaples from documentation but I have no idea how setting color range works.
https://matplotlib.org/2.0.2/examples/pylab_examples/custom_cmap.html
This is the closest to what I need: (Full green from 1.0 to 0.916, full yellow from 0.916 to 0.75 and full red below 0.75)
cdict1 = {'red': ((0.0, 1.0, 1.0),
(0.75, 1.0, 1.0),
(1.0, 0.0, 0.0)),
'green': ((0.0, 0.0, 0.0),
(0.75, 1.0, 1.0),
(0.91666666666, 1.0, 1.0),
(1.0, 1.0, 1.0)),
'blue': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.0),
(1.0, 0.0, 0.0))}
I don't undestand why this colormap is a smooth transition between colors.
To create a colormap with 3 fixed colors with unequal boundaries, the recommended approach uses a BoundaryNorm.
If you really only want to work with a colormap, you could create one from a list of colors.
A LinearSegmentedColormap makes smooth transitions with specific colors at given values. To make it work with fixed colors, these values can be set equal. The function either works the "old" way manipulating rgb values, or with a list of (value, color) pairs (LinearSegmentedColormap.from_list()).
The following example code shows how this can work:
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap, BoundaryNorm, LinearSegmentedColormap
import numpy as np
x, y = np.random.rand(2, 100)
fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(14, 4))
# working with a BoundaryNorm
cmap1 = ListedColormap(['red', 'yellow', 'green'])
norm1 = BoundaryNorm([0, 0.75, 0.916, 1], ncolors=3)
scat1 = ax1.scatter(x, y, c=y, cmap=cmap1, norm=norm1)
plt.colorbar(scat1, ax=ax1, spacing='proportional')
ax1.set_title('working with BoundaryNorm')
# creating a special colormap
colors = ['green' if c > 0.916 else 'red' if c < 0.75 else 'yellow' for c in np.linspace(0, 1, 256)]
cmap2 = ListedColormap(colors)
scat2 = ax2.scatter(x, y, c=y, cmap=cmap2, vmin=0, vmax=1)
plt.colorbar(scat2, ax=ax2)
ax2.set_title('special list of colors')
cmap3 = LinearSegmentedColormap.from_list('', [(0, 'red'), (0.75, 'red'), (0.75, 'yellow'), (0.916, 'yellow'),
(0.916, 'green'), (1, 'green')])
scat3 = ax3.scatter(x, y, c=y, cmap=cmap3, vmin=0, vmax=1)
plt.colorbar(scat3, ax=ax3)
ax3.set_title('LinearSegmentedColormap')
plt.tight_layout()
plt.show()
The spacing='proportional' option of plt.colorbar shows the boundaries at their proportional location. The default shows 3 equally-spaced boundaries together with the values.
I'm attempting to plot 3D line trajectories that evolve over time, and I would like the colors to change to show that passage of time (e.g. from light blue to dark blue). However, there is a distinct lack of tutorials for using matplotlib's Line3DCollection; this is the closest I could find, but all I'm getting is a white line.
Here's my code.
import matplotlib.pyplot as plot
from mpl_toolkits.mplot3d.axes3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Line3DCollection
import numpy as np
# X has shape (3, n)
c = np.linspace(0, 1., num = X.shape[1])[::-1]
a = np.ones(shape = c.shape[0])
r = zip(a, c, c, a) # an attempt to make red vary from light to dark
# r, which contains n tuples of the form (r,g,b,a), looks something like this:
# [(1.0, 1.0, 1.0, 1.0),
# (1.0, 0.99998283232330165, 0.99998283232330165, 1.0),
# (1.0, 0.9999656646466033, 0.9999656646466033, 1.0),
# (1.0, 0.99994849696990495, 0.99994849696990495, 1.0),
# ...,
# (1.0, 1.7167676698312416e-05, 1.7167676698312416e-05, 1.0),
# (1.0, 0.0, 0.0, 1.0)]
fig = plot.figure()
ax = fig.gca(projection = '3d')
points = np.array([X[0], X[1], X[2]]).T.reshape(-1, 1, 3)
segs = np.concatenate([points[:-1], points[1:]], axis = 1)
lc = Line3DCollection(segs, colors = r)
ax.add_collection3d(lc)
ax.set_xlim(-0.45, 0.45)
ax.set_ylim(-0.4, 0.5)
ax.set_zlim(-0.45, 0.45)
plot.show()
However, here's what I get:
Just a bunch of white line segments, no shift in the color. What am I doing wrong? Thanks!
Your code works just fine, here's a bit of a sample. Basically, this is your code with a custom X set.
fig = plot.figure();
ax = fig.gca(projection = '3d')
X = [(0,0,0,1,0),(0,0,1,0,0),(0,1,0,0,0)]
points = np.array([X[0], X[1], X[2]]).T.reshape(-1, 1, 3)
r = [(1.0, 1.0, 1.0, 1.0), (1.0, 0.75, 0.75, 1.0), (1.0, 0.5, 0.5, 1.0), (1.0, 0.25, 0.25, 1.0), (1.0, 0.0, 0.0, 1.0)];
segs = np.concatenate([points[:-1], points[1:]], axis = 1)
ax.add_collection(Line3DCollection(segs,colors=list(r)))
plot.show()
And the plot looks like this:
Wow, so it turns out the problem was that X was actually not of shape (3, n), but rather something like (3, n^10), but I was only plotting n points, hence the color appeared to never change (and why r seems to have extremely small intervals...there were something like 58,000 points when I was plotting only 250).
So yes, it was a bug. Sorry about that; it works fine now.
I have the following code:
import matplotlib.pyplot as plt
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
plt.clf()
plt.pcolor(X, Y, v, cmap=cm)
plt.loglog()
plt.xlabel('X Axis')
plt.ylabel('Y Axis')
plt.colorbar()
plt.show()
So this produces a graph of the values 'v' on the axes X vs Y, using the specified colormap. The X and Y axes are perfect, but the colormap spreads between the min and max of v. I would like to force the colormap to range between 0 and 1.
I thought of using:
plt.axis(...)
To set the ranges of the axes, but this only takes arguments for the min and max of X and Y, not the colormap.
Edit:
For clarity, let's say I have one graph whose values range (0 ... 0.3), and another graph whose values (0.2 ... 0.8).
In both graphs, I will want the range of the colorbar to be (0 ... 1). In both graphs, I want this range of colour to be identical using the full range of cdict above (so 0.25 in both graphs will be the same colour). In the first graph, all colours between 0.3 and 1.0 won't feature in the graph, but will in the colourbar key at the side. In the other, all colours between 0 and 0.2, and between 0.8 and 1 will not feature in the graph, but will in the colourbar at the side.
Using vmin and vmax forces the range for the colors. Here's an example:
import matplotlib as m
import matplotlib.pyplot as plt
import numpy as np
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
x = np.arange(0, 10, .1)
y = np.arange(0, 10, .1)
X, Y = np.meshgrid(x,y)
data = 2*( np.sin(X) + np.sin(3*Y) )
def do_plot(n, f, title):
#plt.clf()
plt.subplot(1, 3, n)
plt.pcolor(X, Y, f(data), cmap=cm, vmin=-4, vmax=4)
plt.title(title)
plt.colorbar()
plt.figure()
do_plot(1, lambda x:x, "all")
do_plot(2, lambda x:np.clip(x, -4, 0), "<0")
do_plot(3, lambda x:np.clip(x, 0, 4), ">0")
plt.show()
Use the CLIM function (equivalent to CAXIS function in MATLAB):
plt.pcolor(X, Y, v, cmap=cm)
plt.clim(-4,4) # identical to caxis([-4,4]) in MATLAB
plt.show()
Not sure if this is the most elegant solution (this is what I used), but you could scale your data to the range between 0 to 1 and then modify the colorbar:
import matplotlib as mpl
...
ax, _ = mpl.colorbar.make_axes(plt.gca(), shrink=0.5)
cbar = mpl.colorbar.ColorbarBase(ax, cmap=cm,
norm=mpl.colors.Normalize(vmin=-0.5, vmax=1.5))
cbar.set_clim(-2.0, 2.0)
With the two different limits you can control the range and legend of the colorbar. In this example only the range between -0.5 to 1.5 is show in the bar, while the colormap covers -2 to 2 (so this could be your data range, which you record before the scaling).
So instead of scaling the colormap you scale your data and fit the colorbar to that.
Using figure environment and .set_clim()
Could be easier and safer this alternative if you have multiple plots:
import matplotlib as m
import matplotlib.pyplot as plt
import numpy as np
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
x = np.arange(0, 10, .1)
y = np.arange(0, 10, .1)
X, Y = np.meshgrid(x,y)
data = 2*( np.sin(X) + np.sin(3*Y) )
data1 = np.clip(data,0,6)
data2 = np.clip(data,-6,0)
vmin = np.min(np.array([data,data1,data2]))
vmax = np.max(np.array([data,data1,data2]))
fig = plt.figure()
ax = fig.add_subplot(131)
mesh = ax.pcolormesh(data, cmap = cm)
mesh.set_clim(vmin,vmax)
ax1 = fig.add_subplot(132)
mesh1 = ax1.pcolormesh(data1, cmap = cm)
mesh1.set_clim(vmin,vmax)
ax2 = fig.add_subplot(133)
mesh2 = ax2.pcolormesh(data2, cmap = cm)
mesh2.set_clim(vmin,vmax)
# Visualizing colorbar part -start
fig.colorbar(mesh,ax=ax)
fig.colorbar(mesh1,ax=ax1)
fig.colorbar(mesh2,ax=ax2)
fig.tight_layout()
# Visualizing colorbar part -end
plt.show()
A single colorbar
The best alternative is then to use a single color bar for the entire plot. There are different ways to do that, this tutorial is very useful for understanding the best option. I prefer this solution that you can simply copy and paste instead of the previous visualizing colorbar part of the code.
fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8,
wspace=0.4, hspace=0.1)
cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8])
cbar = fig.colorbar(mesh, cax=cb_ax)
P.S.
I would suggest using pcolormesh instead of pcolor because it is faster (more infos here ).
I would like to know how to simply reverse the color order of a given colormap in order to use it with plot_surface.
The standard colormaps also all have reversed versions. They have the same names with _r tacked on to the end. (Documentation here.)
The solution is pretty straightforward. Suppose you want to use the "autumn" colormap scheme. The standard version:
cmap = matplotlib.cm.autumn
To reverse the colormap color spectrum, use get_cmap() function and append '_r' to the colormap title like this:
cmap_reversed = matplotlib.cm.get_cmap('autumn_r')
In matplotlib a color map isn't a list, but it contains the list of its colors as colormap.colors. And the module matplotlib.colors provides a function ListedColormap() to generate a color map from a list. So you can reverse any color map by doing
colormap_r = ListedColormap(colormap.colors[::-1])
As of Matplotlib 2.0, there is a reversed() method for ListedColormap and LinearSegmentedColorMap objects, so you can just do
cmap_reversed = cmap.reversed()
Here is the documentation.
As a LinearSegmentedColormaps is based on a dictionary of red, green and blue, it's necessary to reverse each item:
import matplotlib.pyplot as plt
import matplotlib as mpl
def reverse_colourmap(cmap, name = 'my_cmap_r'):
"""
In:
cmap, name
Out:
my_cmap_r
Explanation:
t[0] goes from 0 to 1
row i: x y0 y1 -> t[0] t[1] t[2]
/
/
row i+1: x y0 y1 -> t[n] t[1] t[2]
so the inverse should do the same:
row i+1: x y1 y0 -> 1-t[0] t[2] t[1]
/
/
row i: x y1 y0 -> 1-t[n] t[2] t[1]
"""
reverse = []
k = []
for key in cmap._segmentdata:
k.append(key)
channel = cmap._segmentdata[key]
data = []
for t in channel:
data.append((1-t[0],t[2],t[1]))
reverse.append(sorted(data))
LinearL = dict(zip(k,reverse))
my_cmap_r = mpl.colors.LinearSegmentedColormap(name, LinearL)
return my_cmap_r
See that it works:
my_cmap
<matplotlib.colors.LinearSegmentedColormap at 0xd5a0518>
my_cmap_r = reverse_colourmap(my_cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = my_cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = my_cmap_r, norm=norm, orientation='horizontal')
EDIT
I don't get the comment of user3445587. It works fine on the rainbow colormap:
cmap = mpl.cm.jet
cmap_r = reverse_colourmap(cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = cmap_r, norm=norm, orientation='horizontal')
But it especially works nice for custom declared colormaps, as there is not a default _r for custom declared colormaps. Following example taken from http://matplotlib.org/examples/pylab_examples/custom_cmap.html:
cdict1 = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.1),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 1.0),
(0.5, 0.1, 0.0),
(1.0, 0.0, 0.0))
}
blue_red1 = mpl.colors.LinearSegmentedColormap('BlueRed1', cdict1)
blue_red1_r = reverse_colourmap(blue_red1)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = blue_red1, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = blue_red1_r, norm=norm, orientation='horizontal')
There is no built-in way (yet) of reversing arbitrary colormaps, but one simple solution is to actually not modify the colorbar but to create an inverting Normalize object:
from matplotlib.colors import Normalize
class InvertedNormalize(Normalize):
def __call__(self, *args, **kwargs):
return 1 - super(InvertedNormalize, self).__call__(*args, **kwargs)
You can then use this with plot_surface and other Matplotlib plotting functions by doing e.g.
inverted_norm = InvertedNormalize(vmin=10, vmax=100)
ax.plot_surface(..., cmap=<your colormap>, norm=inverted_norm)
This will work with any Matplotlib colormap.
There are two types of LinearSegmentedColormaps. In some, the _segmentdata is given explicitly, e.g., for jet:
>>> cm.jet._segmentdata
{'blue': ((0.0, 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65, 0, 0), (1, 0, 0)), 'red': ((0.0, 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1), (1, 0.5, 0.5)), 'green': ((0.0, 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1), (0.91, 0, 0), (1, 0, 0))}
For rainbow, _segmentdata is given as follows:
>>> cm.rainbow._segmentdata
{'blue': <function <lambda> at 0x7fac32ac2b70>, 'red': <function <lambda> at 0x7fac32ac7840>, 'green': <function <lambda> at 0x7fac32ac2d08>}
We can find the functions in the source of matplotlib, where they are given as
_rainbow_data = {
'red': gfunc[33], # 33: lambda x: np.abs(2 * x - 0.5),
'green': gfunc[13], # 13: lambda x: np.sin(x * np.pi),
'blue': gfunc[10], # 10: lambda x: np.cos(x * np.pi / 2)
}
Everything you want is already done in matplotlib, just call cm.revcmap, which reverses both types of segmentdata, so
cm.revcmap(cm.rainbow._segmentdata)
should do the job - you can simply create a new LinearSegmentData from that. In revcmap, the reversal of function based SegmentData is done with
def _reverser(f):
def freversed(x):
return f(1 - x)
return freversed
while the other lists are reversed as usual
valnew = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
So actually the whole thing you want, is
def reverse_colourmap(cmap, name = 'my_cmap_r'):
return mpl.colors.LinearSegmentedColormap(name, cm.revcmap(cmap._segmentdata))