Reputation: 311
I need to change the placement of my colorbar ticks and I need help in doing so. I have tried a few things so far but not one of them worked in a satisfactory way.
I have a dataframe of the following type:
import pandas as pd
import random
country_iso = ['RU', 'FR', 'HU', 'AT', 'US', 'ES', 'DE', 'CH', 'LV', 'LU']
my_randoms=[random.uniform(0.0, 0.3) for _ in range (len(country_iso))]
df = pd.DataFrame({'iso':country_iso, 'values':my_randoms})
df.loc[df.iso.str.contains('US')]= ['US', 0]
df['binned']=pd.cut(df['values'], bins=[-0.01, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3], labels=[0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3])
df.binned = pd.to_numeric(df.binned, errors='coerce')
df = df[['iso', 'binned']]
As you can see, this is a dataframe with iso-country values as one column and some as a second column a binned value between 0 and 0.3. The US has the value 0 and I want it to have a distinct colour. I then proceed to create a basemap with a shapefile from natural earth.
from matplotlib import pyplot as plt, colors as clr, cm
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
from matplotlib.collections import PatchCollection
from matplotlib.ticker import FuncFormatter
import numpy as np
shapefile_path = #path to your shapefile
fig, ax = plt.subplots(figsize=(10,20))
m = Basemap(resolution='c', # c, l, i, h, f or None
projection='mill',
llcrnrlat=-62, urcrnrlat=85,
llcrnrlon=-180, urcrnrlon=180)
m.readshapefile(shapefile_path+r'\ne_110m_admin_0_countries', 'areas')
df_poly = pd.DataFrame({
'shapes': [Polygon(np.array(shape), True) for shape in m.areas],
'iso': [gs['ISO_A2'] for gs in m.areas_info]
})
df_poly = df_poly.merge(df, on='iso', how='inner', indicator=True)
cmap = clr.LinearSegmentedColormap.from_list('custom blue', ['#d0dfef', '#24466b'], N=7)
cmap._init()
cmap._lut[0,: ] = np.array([200,5,5,200])/255
pc = PatchCollection(df_poly[df_poly[df.columns[1]].notnull()].shapes, zorder=2)
norm = clr.Normalize()
pc.set_facecolor(cmap(norm(df_poly[df_poly[df.columns[1]].notnull()][df.columns[1]].values)))
ax.add_collection(pc)
mapper = cm.ScalarMappable(norm=norm, cmap=cmap)
mapper.set_array(df_poly[df_poly[df.columns[1]].notnull()][df.columns[1]])
clb = plt.colorbar(mapper, shrink=0.19)
I create a cmap of blues but change the first colour to red. This is because I want countries with the value 0 to have a distinct colour. Everything works splendidly, but if you have a look at the colorbar, the ticks are off-center.
Any chance someone knows how to correct my code? Thanks a lot!
Upvotes: 3
Views: 2379
Reputation: 9820
Cooking down your example to the essentials, you can achieve what you want by extending the range that you pass to your mapper. I guess that it is up to the specific use case whether it makes sense to extend the colormap to negative values. Anyway, here is a full example without Basemap
and shapefiles
, which are not needed for this problem.
from matplotlib import pyplot as plt, colors as clr, cm
import numpy as np
fig= plt.figure()
ax = plt.subplot2grid((1,20),(0,0), colspan=19)
cax = plt.subplot2grid((1,20),(0,19))
upper = 0.3
lower = 0.0
N = 7
cmap = clr.LinearSegmentedColormap.from_list(
'custom blue', ['#d0dfef', '#24466b'], N=N
)
cmap._init()
cmap._lut[0,: ] = np.array([200,5,5,200])/255.0
norm = clr.Normalize()
mapper = cm.ScalarMappable(norm=norm, cmap=cmap)
deltac = (upper-lower)/(2*(N-1))
mapper.set_array(np.linspace(lower-deltac,upper+deltac,10)) #<-- the 10 here is pretty arbitrary
clb = fig.colorbar(mapper, shrink=0.19, cax=cax)
plt.show()
I had some problems with the vertical extend of the colorbar, which is why I chose to use the cax
keyword. Note also that in Python 2 there is a small problem with integer divisions. I therefore changed the division from /255
to /255.0
. The final result looks like this:
Hope this helps.
EDIT:
Apparently the call to norm()
alters the state of the Normalize
object. By providing a new Normalize
object to the ScalarMappable
constructor, the code starts working as intended. I'm still baffled why this happens. Anyway, below the full code to produce the plot and the colorbar (note that I changed the figure size and the colorbar scaling):
from matplotlib import pyplot as plt, colors as clr, cm
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
import pandas as pd
import random
country_iso = ['RU', 'FR', 'HU', 'AT', 'US', 'ES', 'DE', 'CH', 'LV', 'LU']
my_randoms=[random.uniform(0.0, 0.3) for _ in range (len(country_iso))]
df = pd.DataFrame({'iso':country_iso, 'values':my_randoms})
df.loc[df.iso.str.contains('US')]= ['US', 0]
df['binned']=pd.cut(df['values'], bins=[-0.01, 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3], labels=[0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3])
df.binned = pd.to_numeric(df.binned, errors='coerce')
df = df[['iso', 'binned']]
import numpy as np
from matplotlib.collections import PatchCollection
from matplotlib.ticker import FuncFormatter
shapefile_path = 'shapefiles/'
fig, ax = plt.subplots()#figsize=(10,20))
upper = 0.3
lower = 0.0
N = 7
deltac = (upper-lower)/(2*(N-1))
m = Basemap(resolution='c', # c, l, i, h, f or None
projection='mill',
llcrnrlat=-62, urcrnrlat=85,
llcrnrlon=-180, urcrnrlon=180,
ax = ax,
)
m.readshapefile(shapefile_path+r'ne_110m_admin_0_countries', 'areas')
df_poly = pd.DataFrame({
'shapes': [Polygon(np.array(shape), True) for shape in m.areas],
'iso': [gs['ISO_A2'] for gs in m.areas_info]
})
df_poly = df_poly.merge(df, on='iso', how='inner', indicator=True)
cmap = clr.LinearSegmentedColormap.from_list('custom blue', ['#d0dfef', '#24466b'], N=N)
cmap._init()
cmap._lut[0,: ] = np.array([200,5,5,200])/255.0
pc = PatchCollection(df_poly[df_poly[df.columns[1]].notnull()].shapes, zorder=2)
norm = clr.Normalize()
pc.set_facecolor(cmap(norm(df_poly[df_poly[df.columns[1]].notnull()][df.columns[1]].values)))
ax.add_collection(pc)
mapper = cm.ScalarMappable(norm=clr.Normalize(), cmap=cmap)
mapper.set_array([lower-deltac,upper+deltac])
clb = plt.colorbar(mapper, shrink=0.55)
plt.show()
The resulting plot looks like this:
Upvotes: 3