I am trying to obtain a 2D pseudocolor plot with a nonlinear color map (cmap). Independently I want to have a colobar that uses a similar cmap but differently scaled/stretched to avoid overlapping of the colorbar yticks.
The first one I can obtain using some nonlinear norm as an argument of pcolormesh.
But how to get the second part in an efficient way?
Finally, I was able to obtain the desired effect (see the bottom right corner in the below figure) but I am pretty sure that this is not the best/easiest/desired/Pythonic way of doing it.
Is there an easier way of obtaining such an effect?
Figure:
Here is the code that reproduces the above figure:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cbook as cbook
from matplotlib import cm
import copy
def transform_cmap(cmap_basic,
new_cmap_name = None,
cmap_trans_fun = None,
how_many_levels = 256,
ticks = None,
ticks_trans_fun = None):
'''returns new cmap and new ticks locators for transformed ticks.
If ticks_trans_fun is None then ticks locators are linearly transformed
to the [0,1] interval, as a result, cmap is stretched, but when used with
colorbar than linearly spaced ticks are linearly spaced along the colorbar'''
# be sure that cmap is really a cmap
if not isinstance(cmap_basic,colors.Colormap):
try:
cmap_basic = cm.get_cmap(name=cmap_basic)
except:
print('basic_cmap is not a valid cmap or cmap name!')
if cmap_trans_fun is None:
cmap_trans_fun = colors.Normalize()
if new_cmap_name is None:
new_cmap_name = cmap_basic.name+'_new'
c_coords_linear = np.linspace(0,1, how_many_levels)
cmap_trans_fun_copy = copy.deepcopy(cmap_trans_fun)
# deppcopy to avoid overwritting the vmin, vmax values
cmap_trans_fun_copy.autoscale([0,1])
c_coords_after = cmap_trans_fun_copy(c_coords_linear)
c_list_after = cmap_basic(c_coords_after)
new_cmap = colors.LinearSegmentedColormap.from_list(new_cmap_name,
c_list_after,
N=how_many_levels)
if ticks_trans_fun is None:
ticks_trans_fun = colors.Normalize()
ticks_trans_fun_copy = copy.deepcopy(ticks_trans_fun)
ticks_trans_fun_copy.vmin = cmap_trans_fun.vmin
ticks_trans_fun_copy.vmax = cmap_trans_fun.vmax
new_ticks_locators = ticks_trans_fun_copy(ticks)
return new_cmap, new_ticks_locators
###########################################
# Prepare some data
# based on https://matplotlib.org/stable/gallery/userdemo/colormap_normalizations.html#sphx-glr-gallery-userdemo-colormap-normalizations-py
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
# A low hump with a spike coming out of the top right. Needs to have
# z/colour axis on a log scale so we see both hump and spike. linear
# scale only shows the spike.
Z1 = np.exp(-X**2 - Y**2)
Z1b = 2*np.exp(-10*(X-0.5)**2 - 20*(Y-0.5)**2)
Z2 = np.exp(-(X * 10)**2 - (Y * 10)**2)
Z = Z1 + 50 * Z2+ Z1b
Z = Z*10
# data prepared!
###########################################
cbar_ticks = [0,1,10,50,100,200,300,400]
# prepare basic 'linear' cmap from matplotlib avaliable cmap
cmap = 'inferno'
lin_cmap = cm.get_cmap(cmap)
############################################
# prepare nonlinear norm (good for data viz)
gamma = 0.1
nonlin_norm = colors.PowerNorm(gamma=gamma)
nonlin_norm.autoscale(Z)
# prepare nonlinear norm b (better for colorbar)
gamma_b = 0.40
nonlin_norm_b = colors.PowerNorm(gamma=gamma_b)
nonlin_norm.autoscale(Z)
################# PLOT ####################
# create 4 plots in 2x2 grid
fig, axs = plt.subplots(nrows = 2,
ncols = 2,
figsize=(6,5.5),
dpi=108,
squeeze = False)
fs = 8 # the same plot title fontsize
LLL_ax = axs[0,0]
LNL_ax = axs[0,1]
LNN_ax = axs[1,0]
LNNb_ax = axs[1,1]
#------------- Top left plot --------------
LLL_ax.set_title('linear cmap\nlinear norm\nlinear cbar',
fontsize = fs)
LLL_pcm = LLL_ax.pcolormesh(X, Y, Z,
cmap = lin_cmap,
shading='auto')
# colorbar takes LLL_pcm object to figure out colormap and scale
fig.colorbar(LLL_pcm,
ax=LLL_ax,
extend='both',
ticks=cbar_ticks)
#------------- Top right plot -------------
# an easy way of obtaining good color-scaling
# the colorbar shows cmap in a linear way
# the cbar yticks are nonlinearly scaled but
# they are overlapping
LNL_ax.set_title('linear cmap\nnonlinear norm\nlinear cbar cmap (nonlinear ticks)',
fontsize = fs)
nonlin_norm.autoscale(Z)
LNL_pcm = LNL_ax.pcolormesh(X, Y, Z,
cmap = lin_cmap,
norm = nonlin_norm,
shading='auto')
fig.colorbar(LNL_pcm,
ax=LNL_ax,
extend='both',
ticks=cbar_ticks)
#------------- Bottom left plot -----------
# the colorbar cmap is nonlinear
# the cbar yticks are linearly scaled but
# the overall effect is not good
# the cbar yticks are overlapping again
LNN_ax.set_title('linear cmap\nnonlinear norm\nnonlinear cbar cmap (linear ticks)',
fontsize = fs)
LNN_pcm = LNN_ax.pcolormesh(X, Y, Z,
cmap = lin_cmap,
norm = nonlin_norm,
shading='auto')
# create new, nonlinear cmap
nonlin_cmap, new_ticks_coords = transform_cmap(cmap_basic = lin_cmap ,
cmap_trans_fun = nonlin_norm,
how_many_levels = 256,
ticks = cbar_ticks,
ticks_trans_fun = None,
new_cmap_name = 'nonlinear_cmap')
# create object based on new cmap for colorbar
scalar_mappable = cm.ScalarMappable(cmap=nonlin_cmap)
LNN_cbar = fig.colorbar(scalar_mappable,
ax=LNN_ax,
extend='both',
ticks=new_ticks_coords)
# ticks are in correct places but they are normalized to [0,1] interval
# we need to overwrite them with desired labels
LNN_cbar.ax.set_yticklabels(cbar_ticks)
#------------- Bottom right plot ----------
# the colorbar shows cmap in a nonlinear way
# this is different nonlinear scaling than before (nonlin_norm_b)
# the cbar yticks are also nonlinearly scaled
# this is A GOOD LOOKING PLOT
LNNb_ax.set_title('linear cmap\nnonlinear norm\n2nd nonlinear cbar cmap (nonlinear ticks)',
fontsize = fs)
LNNb_pcm = LNNb_ax.pcolormesh(X, Y, Z,
cmap = lin_cmap,
norm = nonlin_norm,
shading='auto')
# this time as the cbar cmap is with different norm than data cmap
# we also need to recalculate positions of the cbar ticks using second norm
nonlin_cmap_b, new_ticks_coords_b = transform_cmap(cmap_basic = lin_cmap ,
cmap_trans_fun = nonlin_norm_b,
how_many_levels = 256,
ticks = cbar_ticks,
ticks_trans_fun = nonlin_norm_b,
new_cmap_name = 'nonlinear_cmap_v2')
scalar_mappable_b = cm.ScalarMappable(cmap=nonlin_cmap_b)
LNNb_cbar = fig.colorbar(scalar_mappable_b,
ax=LNNb_ax,
extend='both',
ticks=new_ticks_coords_b)
LNNb_cbar.ax.set_yticklabels(cbar_ticks)
#------------------------------
fig.tight_layout()
plt.show()
I was using this answer as a base:
Uniform tick labels for non-linear colorbar in Matplotlib
These answers may be useful but were looking too complicated:
Arbirtrary non-linear colorbar using Matplotlib
nonlinear colormap, matplotlib
I have a feeling that wiser usage of norm parameter in pcolor and perhaps in cbar should give me the desired result. Unfortunately, I was not able to obtain it in this way.
Related
I have a dataframe such as below and I am trying to use the example plot code (given below) to generate a similar style line series plot for my dataframe.
df = pd.DataFrame({'x': np.linspace(0, 10, 100),
'run0_Y': np.sin(np.linspace(0, 10, 100)),
'run1_Y': np.cos(np.linspace(0, 10, 100)),
'run2_Y': np.cos(np.linspace(0, 10, 100)),
'run3_Y': np.arctan(np.linspace(0, 10, 100))
})
I would like to generate a plot like below (see code) but I want to
replace the colorbar with my dataframe headings ['run0_Y' ...
'run3_Y'] as legends for each color.
'run0_Y' and 'run1_Y' belongs to the same color but differentiated
with solid line '-k' and dashed line '--k'
I am stuck as to how to plot the line series from my dataframe and
associate each dataframe column to its column header in the colorbar
as legend.
Example plotting code:
import numpy as np
import matplotlib.pyplot as plt
# Use the spectral colormap for examples
cmap = plt.cm.Spectral
# Generate some fake data
N = 100
nlines = 10
x = np.linspace(-np.pi, np.pi, N)
print('x: \n', x)
y = np.linspace(-np.pi, np.pi, nlines)
print('y: \n', y)
# Use np.newaxis to create [N,1] and [1,Nlines] x and y arrays
# Then broadcasting to generate Z with shape [N,Nlines]
z = np.sin(x[:,np.newaxis] + y[np.newaxis,:]/4)
print('z \n', z)
# Use 0-1 values to generate the colors with the linspace method
line_colors = cmap(np.linspace(0,1,nlines))
# We have to generate our own axis to put the colorbar in
# otherwise it "steals" space from the current axis. Please
# let me know if anyone has found another way around this,
# because the custom axes generation is the only way I've
# figured out.
from matplotlib.gridspec import GridSpec
# fig = plt.figure(figsize = (12,6))
# nrows = 2
# gs = GridSpec(nrows,2,width_ratios=[50,1])
# ax = [plt.subplot(gs[i,0]) for i in range(nrows)]
# cbax1 = plt.subplot(gs[1,1])
# # First, plot lines w/ legend
# a = ax[0]
# a.set_title('Labeling with a legend')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=3,label='{:4.1f}'.format(y[i]))
# leg = a.legend(loc='center left', bbox_to_anchor=(1, 0.5), ncol=2)
# leg.set_title('Y')
# # Next, plot with colorbar
# a = ax[1]
# a.set_title('Labeling with a "continuous" colorbar')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=3,label='{:3.1f}'.format(y[i]))
# # Generate fake ScalarMappable for colorbar
# sm = plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=y[0],vmax=y[-1]))
# sm.set_array([]) # You have to set a dummy-array for this to work...
# cbar = plt.colorbar(sm, cax=cbax1)
# cbar.set_label('Y')
# cbar.set_ticks(y)
# cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# # Moves colorbar closer to main axis by adjusting width-spacing between subplot axes.
# fig.subplots_adjust(wspace=0.05, hspace=0.4)
# # Set axis limits
# for a in ax:
# a.set_xlim(-np.pi, np.pi)
fig = plt.figure(figsize = (12,6))
nrows = 1
gs = GridSpec(nrows,2,width_ratios=[50,1])
ax = [plt.subplot(gs[i,0]) for i in range(nrows)]
cbax = [plt.subplot(gs[i,1]) for i in range(nrows)]
# We'll use the same fake ScalarMappable and colormap for each example
from matplotlib.colors import ListedColormap
cmap2 = ListedColormap(line_colors)
sm = plt.cm.ScalarMappable(cmap=cmap2,
norm=plt.Normalize(vmin=y[0],vmax=y[-1]))
sm.set_array([])
# # Discrete colorbar with default spacing
# a = ax[0]
# a.set_title('Labeling with a discrete colorbar')
# for i in range(nlines):
# a.plot(x, z[:,i], c=line_colors[i],lw=2,label='{:4.1}'.format(y[i]))
# cbar = plt.colorbar(sm, cax=cbax[0])
# cbar.set_label('Y')
# cbar.set_ticks(y)
# cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# Discrete colorbar with centered ticks
# a = ax[1]
a = ax[0]
a.set_title('Labeling with a discrete colorbar & centered labels')
for i in range(nlines):
a.plot(x, z[:,i], c=line_colors[i],lw=2,label='{:4.1}'.format(y[i]))
# Generate custom bounds so that ticks are centered
dy = y[1]-y[0]
ybounds = np.linspace(y[0]-dy/2., y[-1]+dy/2., nlines+1)
cbar = plt.colorbar(sm, cax=cbax[0], boundaries=ybounds)
cbar.set_label('Y')
cbar.set_ticks(y)
cbar.set_ticklabels(['{:4.1f}'.format(yi) for yi in y]) # Make 'em nicer-looking
# Set axis limits
for a in ax:
a.set_xlim(-np.pi, np.pi)
# Moves colorbar closer to main axis by adjusting width-spacing between subplot axes.
fig.subplots_adjust(wspace=0.05, hspace=0.4)
plt.show()
source: https://pyhogs.github.io/colormap-examples.html
I am attempting to produce a plot like this which combines a cartesian scatter plot and a polar histogram. (Radial lines optional)
A similar solution (by Nicolas Legrand) exists for looking at differences in x and y (code here), but we need to look at ratios (i.e. x/y).
More specifically, this is useful when we want to look at the relative risk measure which is the ratio of two probabilities.
The scatter plot on it's own is obviously not a problem, but the polar histogram is more advanced.
The most promising lead I have found is this central example from the matplotlib gallery here
I have attempted to do this, but have run up against the limits of my matplotlib skills. Any efforts moving towards this goal would be great.
I'm sure that others will have better suggestions, but one method that gets something like you want (without the need for extra axes artists) is to use a polar projection with a scatter and bar chart together. Something like
import matplotlib.pyplot as plt
import numpy as np
x = np.random.uniform(size=100)
y = np.random.uniform(size=100)
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi, bins=np.linspace(0, np.pi/2, 21), density=True)
colors = plt.cm.Spectral(h / h.max())
ax = plt.subplot(111, projection='polar')
ax.scatter(phi, r, marker='.')
ax.bar(b[:-1], h, width=b[1:] - b[:-1],
align='edge', bottom=np.max(r) + 0.2, color=colors)
# Cut off at 90 degrees
ax.set_thetamax(90)
# Set the r grid to cover the scatter plot
ax.set_rgrids([0, 0.5, 1])
# Let's put a line at 1 assuming we want a ratio of some sort
ax.set_thetagrids([45], [1])
which will give
It is missing axes labels and some beautification, but it might be a place to start. I hope it is helpful.
You can use two axes on top of each other:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.8,.8], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-1)
ax2.set_thetamax(90)
plt.show()
Ok. Thanks to the answer from Nicolas, and the answer from tomjn I have a working solution :)
import numpy as np
import matplotlib.pyplot as plt
# Scatter data
n = 50
x = 0.3 + np.random.randn(n)*0.1
y = 0.4 + np.random.randn(n)*0.02
def radial_corner_plot(x, y, n_hist_bins=51):
"""Scatter plot with radial histogram of x/y ratios"""
# Axis setup
fig = plt.figure(figsize=(6,6))
ax1 = fig.add_axes([0.1,0.1,.6,.6], label="cartesian")
ax2 = fig.add_axes([0.1,0.1,.8,.8], projection="polar", label="polar")
ax2.set_rorigin(-20)
ax2.set_thetamax(90)
# define useful constant
offset_in_radians = np.pi/4
def rotate_hist_axis(ax):
"""rotate so that 0 degrees is pointing up and right"""
ax.set_theta_offset(offset_in_radians)
ax.set_thetamin(-45)
ax.set_thetamax(45)
return ax
# Convert scatter data to histogram data
r = np.sqrt(x**2 + y**2)
phi = np.arctan2(y, x)
h, b = np.histogram(phi,
bins=np.linspace(0, np.pi/2, n_hist_bins),
density=True)
# SCATTER PLOT -------------------------------------------------------
ax1.scatter(x,y)
ax1.set(xlim=[0, 1], ylim=[0, 1], xlabel="x", ylabel="y")
ax1.spines['right'].set_visible(False)
ax1.spines['top'].set_visible(False)
# HISTOGRAM ----------------------------------------------------------
ax2 = rotate_hist_axis(ax2)
# rotation of axis requires rotation in bin positions
b = b - offset_in_radians
# plot the histogram
bars = ax2.bar(b[:-1], h, width=b[1:] - b[:-1], align='edge')
def update_hist_ticks(ax, desired_ratios):
"""Update tick positions and corresponding tick labels"""
x = np.ones(len(desired_ratios))
y = 1/desired_ratios
phi = np.arctan2(y,x) - offset_in_radians
# define ticklabels
xticklabels = [str(round(float(label), 2)) for label in desired_ratios]
# apply updates
ax2.set(xticks=phi, xticklabels=xticklabels)
return ax
ax2 = update_hist_ticks(ax2, np.array([1/8, 1/4, 1/2, 1, 2, 4, 8]))
# just have radial grid lines
ax2.grid(which="major", axis="y")
# remove bin count labels
ax2.set_yticks([])
return (fig, [ax1, ax2])
fig, ax = radial_corner_plot(x, y)
Thanks for the pointers!
How to reduce the colorbar limit when used with contourf ? The color bound from the graphs itself are well set with "vmin" and "vmax", but the colorbar bounds are not modified.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(20)
y = np.arange(20)
data = x[:,None]+y[None,:]
X,Y = np.meshgrid(x,y)
vmin = 0
vmax = 15
#My attempt
fig,ax = plt.subplots()
contourf_ = ax.contourf(X,Y,data, 400, vmin=vmin, vmax=vmax)
cbar = fig.colorbar(contourf_)
cbar.set_clim( vmin, vmax )
# With solution from https://stackoverflow.com/questions/53641644/set-colorbar-range-with-contourf
levels = np.linspace(vmin, vmax, 400+1)
fig,ax = plt.subplots()
contourf_ = ax.contourf(X,Y,data, levels=levels, vmin=vmin, vmax=vmax)
cbar = fig.colorbar(contourf_)
plt.show()
solution from "Set Colorbar Range in matplotlib" works for pcolormesh, but not for contourf. The result I want looks like the following, but using contourf.
fig,ax = plt.subplots()
contourf_ = ax.pcolormesh(X,Y,data[1:,1:], vmin=vmin, vmax=vmax)
cbar = fig.colorbar(contourf_)
Solution from "set colorbar range with contourf" would be ok if the limit were extended, but not if they are reduced.
I am using matplotlib 3.0.2
The following always produces a bar with colours that correspond to the colours in the graph, but shows no colours for values outside of the [vmin,vmax] range.
It can be edited (see inline comment) to give you exactly the result you want, but that the colours of the bar then still correspond to the colours in the graph, is only due to the specific colour map that's used (I think):
# Start copied from your attempt
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(20)
y = np.arange(20)
data = x[:, None] + y[None, :]
X, Y = np.meshgrid(x, y)
vmin = 0
vmax = 15
fig, ax = plt.subplots()
# Start of solution
from matplotlib.cm import ScalarMappable
levels = 400
level_boundaries = np.linspace(vmin, vmax, levels + 1)
quadcontourset = ax.contourf(
X, Y, data,
level_boundaries, # change this to `levels` to get the result that you want
vmin=vmin, vmax=vmax
)
fig.colorbar(
ScalarMappable(norm=quadcontourset.norm, cmap=quadcontourset.cmap),
ticks=range(vmin, vmax+5, 5),
boundaries=level_boundaries,
values=(level_boundaries[:-1] + level_boundaries[1:]) / 2,
)
Always correct solution that can't handle values outside [vmin,vmax]:
Requested solution:
I am not sure how long it has been there, but in matplotlib 3.5.0 in contourf there is an "extend" option which makes a cutesy little arrow on the colorbar. See the contourf help page. In your scenario we can do
fig,ax = plt.subplots()
contourf_ = ax.contourf(X,Y,data, levels=np.linspace(vmin,vmax,400),extend='max')
cbar = fig.colorbar(contourf_,ticks=range(vmin, vmax+3, 3))
i have a little problem to create a subplot loop.
The following code show my result for one plot.... So it starts with a dayloop than with a hour loop (8 timesteps).
If i run the code i get a nice QUiver plot with the colorbar.
for dd in range(1,15):
day=str(dd)
readfile=fns[files_indizes[dd]]
if dd < 10:
nc_u_comp = NetCDFFile(ROOT+u_comp1+'0'+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+'0'+day+comp)
else:
nc_u_comp = NetCDFFile(ROOT+u_comp1+day+comp)
nc_v_comp = NetCDFFile(ROOT+v_comp1+day+comp)
time = nc_u_comp.variables['time'][:]
index=readfile.find(comp)
index=index+len(comp)
date=readfile[index-14:index-6]
plt.clf()
for tt in range(0,len(time)):
if tt < 10:
h =str(0)+str(tt)
else:
h=str(tt)
varU=nc_u_comp.variables['u10'][tt,:,:]
varV=nc_v_comp.variables['v10'][tt,:,:]
lat = nc_u_comp.variables['latitude'][:]
lon = nc_u_comp.variables['longitude'][:]
plt.rcParams["figure.figsize"] = [10,10]
#plane projection of the world
#map with box size (defintion on the top)
box = sgeom.box(minx=llcrnrlon, maxx=urcrnrlon, miny=llcrnrlat, maxy=urcrnrlat)
x0, y0, x1, y1 = box.bounds
#Map plot. The middel of the map is central_longitude
#proj = ccrs.PlateCarree(central_longitude=0)
proj=ccrs.PlateCarree()
#Change middelpoint of the map
box_proj = ccrs.PlateCarree(central_longitude=0)
ax2 = plt.axes(projection=proj)
ax2.set_extent([x0, x1, y0, y1], box_proj)
ax2.add_feature(cartopy.feature.BORDERS, linestyle='-', alpha=.5)
ax2.coastlines(resolution='50m')
#Definition of the scale_bar
gl = ax2.gridlines(ccrs.PlateCarree(), \
linestyle='--', alpha=1, linewidth=0.5, draw_labels=True)
gl.xlabels_top = False
gl.ylabels_right = False
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
magnitude = (varU ** 2 + varV ** 2) ** 0.5
strm =plt.streamplot(lon , lat , varU, varV, linewidth=2, density=2, color=magnitude)
cbar= plt.colorbar()
cbar.set_label('$m/s$')
name='Wind in 10 m '+ date + h+' UTC'
ax2.set_aspect('auto')
plt.title(name, y=1)
Now i want to create an 2x4 Subplot array with a colorbar allocate to the complete Subplot array.
I find some infromation in the internet, but it doesn't run with my code. Maybe someone can help me?
This shows how to plot an array of simple Cartopy maps in 4 rows 2 columns. Also shows how to plot a colorbar to accompany the maps array. Hope it helps.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import matplotlib as mpl
# create figure with figsize big enough to accomodate all maps, labels, etc.
fig = plt.figure(figsize=(8, 10), tight_layout=False)
# define plot array's arrangement
columns = 2
rows = 4
# set projection to use
projex = ccrs.PlateCarree()
# set the colormap and norm for
# the colorbar to use
cmap1 = mpl.cm.magma
norm1 = mpl.colors.Normalize(vmin=0, vmax=100)
def plotmymap(axs):
# your plot specs of each map should replace this
img = np.random.randint(100, size=(15, 30)) # 2d array of random values (1-100)
# render image on current axis
plims = plt.imshow(img, extent=[-180,180,-90,90], alpha=0.5, cmap=cmap1, norm=norm1)
axs.set_global()
axs.coastlines()
# add title to the map
axs.set_title("Map_"+str(i))
return plims # for use by colorbar
for i in range(1, columns*rows +1):
# add a subplot into the array of plots
ax = fig.add_subplot(rows, columns, i, projection=projex)
plims = plotmymap(ax) # a simple maps is created on subplot
# add a subplot for vertical colorbar
bottom, top = 0.1, 0.9
left, right = 0.1, 0.8
fig.subplots_adjust(top=top, bottom=bottom, left=left, right=right, hspace=0.15, wspace=0.25)
cbar_ax = fig.add_axes([0.85, bottom, 0.05, top-bottom])
fig.colorbar(plims, cax=cbar_ax) # plot colorbar
plt.show() # this plot all the maps
The resulting plots:
This question already has answers here:
Shifted colorbar matplotlib
(1 answer)
Defining the midpoint of a colormap in matplotlib
(10 answers)
Closed 5 years ago.
For my current project I need a heat map. The heat map needs a scalable color palette, because the values are interesting only in a small range. That means, even if I have values from 0 to 1, interesting is only the part between 0.6 and 0.9; so I would like to scale the heat map colors accordingly, plus show the scale next to the chart.
In Matplotlib I had no way of setting the mid point of a color palette except for overloading the original class, like shown here in the matplotlib guide.
This is exactly what I need, but without the disadvantages of the unclean data structure in Matplotlib.
So I tried Bokeh.
In five minutes I achieved more than with Matplotlib in an hour, however, I got stuck when I wanted to show the color scale next to the heatmap and when I wanted to change the scale of the color palette.
So, here are my questions:
How can I scale the color palette in Bokeh or Matplotlib?
Is there a way to display the annotated color bar next to the heatmap?
import pandas
scores_df = pd.DataFrame(myScores, index=c_range, columns=gamma_range)
import bkcharts
from bokeh.palettes import Inferno256
hm = bkcharts.HeatMap(scores_df, palette=Inferno256)
# here: how to insert a color bar?
# here: how to correctly scale the inferno256 palette?
hm.ylabel = "C"
hm.xlabel = "gamma"
bkcharts.output_file('heatmap.html')
Following Aarons tips, i now implemented it as follows:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from bokeh.palettes import Inferno256
def print_scores(scores, gamma_range, C_range):
# load a color map
# find other colormaps here
# https://docs.bokeh.org/en/latest/docs/reference/palettes.html
cmap = colors.ListedColormap(Inferno256, len(Inferno256))
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
# adjust lower, midlle and upper bound of the colormap
cmin = np.percentile(scores, 10)
cmid = np.percentile(scores, 75)
cmax = np.percentile(scores, 99)
bounds = np.append(np.linspace(cmin, cmid), np.linspace(cmid, cmax))
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=len(Inferno256))
pcm = ax.pcolormesh(np.log10(gamma_range),
np.log10(C_range),
scores,
norm=norm,
cmap=cmap)
fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')
plt.show()
ImportanceOfBeingErnest correctly pointed out that my first comment wasn't entirely clear (or accurately worded)..
Most plotting functions in mpl have a kwarg: norm= this denotes a class (subclass of mpl.colors.Normalize) that will map your array of data to the values [0 - 1] for the purpose of mapping to the colormap, but not actually impact the numerical values of the data. There are several built in subclasses, and you can also create your own. For this application, I would probably utilize BoundaryNorm. This class maps N-1 evenly spaced colors to the space between N discreet boundaries.
I have modified the example slightly to better fit your application:
#adaptation of https://matplotlib.org/users/colormapnorms.html#discrete-bounds
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
from matplotlib.mlab import bivariate_normal
#example data
N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = (bivariate_normal(X, Y, 1., 1., 1.0, 1.0))**2 \
- 0.4 * (bivariate_normal(X, Y, 1.0, 1.0, -1.0, 0.0))**2
Z1 = Z1/0.03
'''
BoundaryNorm: For this one you provide the boundaries for your colors,
and the Norm puts the first color in between the first pair, the
second color between the second pair, etc.
'''
fig, ax = plt.subplots(3, 1, figsize=(8, 8))
ax = ax.flatten()
# even bounds gives a contour-like effect
bounds = np.linspace(-1, 1)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[0].pcolormesh(X, Y, Z1,
norm=norm,
cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[0], extend='both', orientation='vertical')
# clipped bounds emphasize particular region of data:
bounds = np.linspace(-.2, .5)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[1].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[1], extend='both', orientation='vertical')
# now if we want 0 to be white still, we must have 0 in the middle of our array
bounds = np.append(np.linspace(-.2, 0), np.linspace(0, .5))
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax[2].pcolormesh(X, Y, Z1, norm=norm, cmap='RdBu_r')
fig.colorbar(pcm, ax=ax[2], extend='both', orientation='vertical')
fig.show()