Create colormap from dictionary - python

Am using geopandas to plot a map with categorical colors. geopandas is an interface to matplotlib so the same structures should work. What I'm after is to have each key display in a specific color. Random color from a predefined cm is fine, as long as the No data can be made grey.
keys = ['A', 'B', 'C', 'D', 'No data']
color_range = list(np.linspace(0, 1, len(keys), endpoint=False))
colors = [plt.cm.Pastel1(x) for x in color_range]
color_dict = dict(zip(keys, colors))
color_dict['No data'] = 'lightgrey'
The dictionary I get from this looks correct:
{'A': (0.98, 0.70, 0.68, 1.0),
'B': (0.70, 0.80, 0.89, 1.0),
'C': (0.87, 0.79, 0.89, 1.0),
'D': (1.0, 1.0, 0.8, 1.0),
'No data': 'lightgrey'}
When I try to convert it to a colormap, I get a KeyError: 0
cmap = LinearSegmentedColormap.from_list('mycmap', color_dict)
My final plot command is:
merged_df.plot(column='GROUP', categorical=True, legend=True, cmap=cmap)

Related

How does matplotlib custom colormaps work

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.

how do I avoid the overlapping error bars and replace X and Y axis?

I am new to matplotlib and I am asking for your help to solve my little problem. I am sharing the graph below, here are the questions:
1- I want x-axis and y-axis replace
2- And most important for me is that errorbars should be horizontal (in graph below these are vertical).
Some errorbars in the graph is overlapping and I tried to avoid this problem using transform command. As I said before if I can manage the replacement of X and Y axis I would be happy.
Below I am sharing the code I wrote:
import ax as ax
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.transforms import Affine2D
y_values = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
p1 = [1, 0.77, 0.67, 0.85, 0.78, 1.05, 0.63]
p2 = [3, 2, 1.5, 1.20, 1.10, 1.40, 1.10]
x_err = [0.1, 0.2, 0.4, 0.5, 0.3, 0.2, 0.3]
y_err = [0.6, 0.2, 0.4, 0.5, 0.3, 0.2, 0.3]
fig, ax = plt.subplots()
trans1 = Affine2D().translate(-0.1, 0.0) + ax.transData
trans2 = Affine2D().translate(+0.1, 0.0) + ax.transData
er1 = ax.errorbar(y_values, p1, x_err, marker="o", linestyle="none", transform=trans1)
er2 = ax.errorbar(y_values, p2, y_err, marker="o", linestyle="none", transform=trans2)
errorbar plot

Python heatmap with intermediate color by percentile

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()

Matplotlib Line3DCollection for time-varying colors

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.

Reverse colormap in matplotlib

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))

Categories