I am trying to make a filled contour for a dataset. It should be fairly straightforward:
plt.contourf(x, y, z, label = 'blah', cm = matplotlib.cm.RdBu)
However, what do I do if my dataset is not symmetric about 0? Let's say I want to go from blue (negative values) to 0 (white), to red (positive values). If my dataset goes from -8 to 3, then the white part of the color bar, which should be at 0, is in fact slightly negative. Is there some way to shift the color bar?
1 Answer 1
First off, there's more than one way to do this.
- Pass an instance of
DivergingNorm
as thenorm
kwarg. - Use the
colors
kwarg tocontourf
and manually specify the colors - Use a discrete colormap constructed with
matplotlib.colors.from_levels_and_colors
.
The simplest way is the first option. It is also the only option that allows you to use a continuous colormap.
The reason to use the first or third options is that they will work for any type of matplotlib plot that uses a colormap (e.g. imshow
, scatter
, etc).
The third option constructs a discrete colormap and normalization object from specific colors. It's basically identical to the second option, but it will a) work with other types of plots than contour plots, and b) avoids having to manually specify the number of contours.
As an example of the first option (I'll use imshow
here because it makes more sense than contourf
for random data, but contourf
would have identical usage other than the interpolation
option.):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import DivergingNorm
data = np.random.random((10,10))
data = 10 * (data - 0.8)
fig, ax = plt.subplots()
im = ax.imshow(data, norm=DivergingNorm(0), cmap=plt.cm.seismic, interpolation='none')
fig.colorbar(im)
plt.show()
first option result
As an example of the third option (notice that this gives a discrete colormap instead of a continuous colormap):
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import from_levels_and_colors
data = np.random.random((10,10))
data = 10 * (data - 0.8)
num_levels = 20
vmin, vmax = data.min(), data.max()
midpoint = 0
levels = np.linspace(vmin, vmax, num_levels)
midp = np.mean(np.c_[levels[:-1], levels[1:]], axis=1)
vals = np.interp(midp, [vmin, midpoint, vmax], [0, 0.5, 1])
colors = plt.cm.seismic(vals)
cmap, norm = from_levels_and_colors(levels, colors)
fig, ax = plt.subplots()
im = ax.imshow(data, cmap=cmap, norm=norm, interpolation='none')
fig.colorbar(im)
plt.show()
third option result
-
4Should be added to MatplotlibMoritz– Moritz2015年05月13日 11:37:07 +00:00Commented May 13, 2015 at 11:37
-
@Joe Kington, that was really great! I am copying your class definition for an open-source project and put your name as the author. Hope you would be okay with it.Asif Rehan– Asif Rehan2015年06月15日 05:03:28 +00:00Commented Jun 15, 2015 at 5:03
-
1@user832 - Go for it! However, Paul Hobson put together a much more complete version of the same general thing as a recent pull request to mpl: github.com/matplotlib/matplotlib/pull/3858 Depending on the functionality you need, you're probably better off basing things on that instead.Joe Kington– Joe Kington2015年06月15日 16:53:50 +00:00Commented Jun 15, 2015 at 16:53
-
1As @JoeKington mentions above, MidpointNormalize, does not handle a number of edge cases. I needed a solution that worked with a starting array containing all zeros and then updated as the values change over time. I ended up adopting DivergingNorm from github.com/matplotlib/matplotlib/pull/5054 to handle this case. A robust solution to this issue is apparently so difficult that development is no longer ongoing. It seems odd, since setting 0 as the centerpoint is a common use case when using a diverging palette with positive and negative data.Todd Johnson– Todd Johnson2018年09月03日 16:05:18 +00:00Commented Sep 3, 2018 at 16:05
-
4For anyone using this great answer today, DivergingNorm has been renamed to TwoSlopeNorm after 3.2 with the DivergingNorm now deprecated.Ryan Ward– Ryan Ward2022年06月27日 09:50:43 +00:00Commented Jun 27, 2022 at 9:50