I am struggling to plot my data using pyplot.imshow
. I use the 'RdBu_r' colormap, and I need the white color to be at value 1 on a logarithmic scale which is not centered at 1. But how can I do it?
I tried 'center=1' which works for seaborn, but there is no such attribute in matplotlib. I also tried this:
import matplotlib.pyplot as plt
im=plt.imshow(proportion, cmap="RdBu_r", norm=LogNorm(), vmin=0.01, vmax=10)
axs=plt.gca()
cb = plt.colorbar(im, ax=axs,extend="both")
where proportion
is my data array, ranging from 0.01 to 10. However there seems to be no way to specify that the white should be at 1 on this scale.
Is there a way to do that?
Note again that I need to make use a gradient of colors here and a logarithmic normalization.
There are some questions and answers about defining a midpoint on a colorscale. Especially this one, which is also now part of the matplotlib documentation.
The idea is to subclass matplotlib.colors.Normalize
and let it take a further argument midpoint
. This can then be used to linearly interpolate the two ranges on either side of the midpoint to the ranges [0,0.5]
and [0.5,1]
.
To have a midpoint on a logarithmic scale, we can in principle do the same thing, just that we subclass matplotlib.colors.LogNorm
and take the logarithm of all values, then interpolate this logarithm on the ranges [0,0.5]
and [0.5,1]
.
In the following example we have data between 0.001
and 10
. Using the usual LogNorm
this results in the middle of the colormap (white in the case of the RdBu colormap) to be at 0.1
. If we want to have white at 1
, we specify 1
as the midpoint in the MidPointLogNorm
.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x,y = np.meshgrid(np.linspace(-3,0,19), np.arange(10))
f = lambda x,y : 10**x*(1+y)
z = f(x,y)
fig, (ax,ax2) = plt.subplots(ncols=2, figsize=(12,4.8))
im = ax.pcolormesh(x,y,z, cmap="RdBu_r", norm=LogNorm(vmin=z.min(), vmax=z.max()))
fig.colorbar(im, ax=ax)
ax.set_title("LogNorm")
class MidPointLogNorm(LogNorm):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
LogNorm.__init__(self,vmin=vmin, vmax=vmax, clip=clip)
self.midpoint=midpoint
def __call__(self, value, clip=None):
# I'm ignoring masked values and all kinds of edge cases to make a
# simple example...
x, y = [np.log(self.vmin), np.log(self.midpoint), np.log(self.vmax)], [0, 0.5, 1]
return np.ma.masked_array(np.interp(np.log(value), x, y))
im2 = ax2.pcolormesh(x,y,z, cmap="RdBu_r",
norm=MidPointLogNorm(vmin=z.min(), vmax=z.max(), midpoint=1))
fig.colorbar(im2, ax=ax2)
ax2.set_title("MidPointLogNorm")
plt.show()
MidPointLogNorm
we need to take care of nan values, as shown in this question.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LogNorm
x,y = np.meshgrid(np.linspace(-3,0,19), np.arange(10))
f = lambda x,y : 10**x*(1+y)
z = f(x,y)
z[1:3,1:3] = np.NaN
#since nan values cannot be used on a log scale, we need to change them to
# something other than nan,
replace = np.nanmax(z)+900
z = np.where(np.isnan(z), replace, z)
# now we can mask the array
z = np.ma.masked_where(z == replace, z)
fig, (ax,ax2) = plt.subplots(ncols=2, figsize=(12,4.8))
im = ax.pcolormesh(x,y,z, cmap="RdBu_r", norm=LogNorm(vmin=z.min(), vmax=z.max()))
fig.colorbar(im, ax=ax)
ax.set_title("LogNorm")
class MidPointLogNorm(LogNorm):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
LogNorm.__init__(self,vmin=vmin, vmax=vmax, clip=clip)
self.midpoint=midpoint
def __call__(self, value, clip=None):
result, is_scalar = self.process_value(value)
x, y = [np.log(self.vmin), np.log(self.midpoint), np.log(self.vmax)], [0, 0.5, 1]
return np.ma.array(np.interp(np.log(value), x, y), mask=result.mask, copy=False)
im2 = ax2.pcolormesh(x,y,z, cmap="RdBu_r",
norm=MidPointLogNorm(vmin=z.min(), vmax=z.max(), midpoint=1))
fig.colorbar(im2, ax=ax2)
ax2.set_title("MidPointLogNorm")
plt.show()