I know this is late to the game, but I just went through this process and came up with a solution that perhaps less robust than subclassing normalize, but much simpler. I thought it'd be good to share it here for posterity.

The function

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid

def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
    '''
    Function to offset the "center" of a colormap. Useful for
    data with a negative min and positive max and you want the
    middle of the colormap's dynamic range to be at zero.

    Input
    -----
      cmap : The matplotlib colormap to be altered
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower offset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to 
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax / (vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highest point in the colormap's range.
          Defaults to 1.0 (no upper offset). Should be between
          `midpoint` and 1.0.
    '''
    cdict = {
        'red': [],
        'green': [],
        'blue': [],
        'alpha': []
    }

    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)

    # shifted index to match the data
    shift_index = np.hstack([
        np.linspace(0.0, midpoint, 128, endpoint=False), 
        np.linspace(midpoint, 1.0, 129, endpoint=True)
    ])

    for ri, si in zip(reg_index, shift_index):
        r, g, b, a = cmap(ri)

        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))

    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)

    return newcmap

An example

biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))

orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')

fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
                label_mode="1", share_all=True,
                cbar_location="right", cbar_mode="each",
                cbar_size="7%", cbar_pad="2%")

# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)

im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)

im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)

im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)

for ax in grid:
    ax.set_yticks([])
    ax.set_xticks([])

Results of the example:

Answer from Paul H on Stack Overflow
Top answer
1 of 11
101

I know this is late to the game, but I just went through this process and came up with a solution that perhaps less robust than subclassing normalize, but much simpler. I thought it'd be good to share it here for posterity.

The function

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import AxesGrid

def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'):
    '''
    Function to offset the "center" of a colormap. Useful for
    data with a negative min and positive max and you want the
    middle of the colormap's dynamic range to be at zero.

    Input
    -----
      cmap : The matplotlib colormap to be altered
      start : Offset from lowest point in the colormap's range.
          Defaults to 0.0 (no lower offset). Should be between
          0.0 and `midpoint`.
      midpoint : The new center of the colormap. Defaults to 
          0.5 (no shift). Should be between 0.0 and 1.0. In
          general, this should be  1 - vmax / (vmax + abs(vmin))
          For example if your data range from -15.0 to +5.0 and
          you want the center of the colormap at 0.0, `midpoint`
          should be set to  1 - 5/(5 + 15)) or 0.75
      stop : Offset from highest point in the colormap's range.
          Defaults to 1.0 (no upper offset). Should be between
          `midpoint` and 1.0.
    '''
    cdict = {
        'red': [],
        'green': [],
        'blue': [],
        'alpha': []
    }

    # regular index to compute the colors
    reg_index = np.linspace(start, stop, 257)

    # shifted index to match the data
    shift_index = np.hstack([
        np.linspace(0.0, midpoint, 128, endpoint=False), 
        np.linspace(midpoint, 1.0, 129, endpoint=True)
    ])

    for ri, si in zip(reg_index, shift_index):
        r, g, b, a = cmap(ri)

        cdict['red'].append((si, r, r))
        cdict['green'].append((si, g, g))
        cdict['blue'].append((si, b, b))
        cdict['alpha'].append((si, a, a))

    newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict)
    plt.register_cmap(cmap=newcmap)

    return newcmap

An example

biased_data = np.random.random_integers(low=-15, high=5, size=(37,37))

orig_cmap = matplotlib.cm.coolwarm
shifted_cmap = shiftedColorMap(orig_cmap, midpoint=0.75, name='shifted')
shrunk_cmap = shiftedColorMap(orig_cmap, start=0.15, midpoint=0.75, stop=0.85, name='shrunk')

fig = plt.figure(figsize=(6,6))
grid = AxesGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.5,
                label_mode="1", share_all=True,
                cbar_location="right", cbar_mode="each",
                cbar_size="7%", cbar_pad="2%")

# normal cmap
im0 = grid[0].imshow(biased_data, interpolation="none", cmap=orig_cmap)
grid.cbar_axes[0].colorbar(im0)
grid[0].set_title('Default behavior (hard to see bias)', fontsize=8)

im1 = grid[1].imshow(biased_data, interpolation="none", cmap=orig_cmap, vmax=15, vmin=-15)
grid.cbar_axes[1].colorbar(im1)
grid[1].set_title('Centered zero manually,\nbut lost upper end of dynamic range', fontsize=8)

im2 = grid[2].imshow(biased_data, interpolation="none", cmap=shifted_cmap)
grid.cbar_axes[2].colorbar(im2)
grid[2].set_title('Recentered cmap with function', fontsize=8)

im3 = grid[3].imshow(biased_data, interpolation="none", cmap=shrunk_cmap)
grid.cbar_axes[3].colorbar(im3)
grid[3].set_title('Recentered cmap with function\nand shrunk range', fontsize=8)

for ax in grid:
    ax.set_yticks([])
    ax.set_xticks([])

Results of the example:

2 of 11
61

Note that in matplotlib version 3.2+ the TwoSlopeNorm class was added. I think it covers your use case. It can be used like this:

from matplotlib import colors
divnorm=colors.TwoSlopeNorm(vmin=-5., vcenter=0., vmax=10)
pcolormesh(your_data, cmap="coolwarm", norm=divnorm)

In matplotlib 3.1 the class was called DivergingNorm.

๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.2.2 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations_custom.html
Colormap Normalizations Custom โ€” Matplotlib 3.2.2 documentation
# From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
GitHub
github.com โ€บ matplotlib โ€บ matplotlib โ€บ issues โ€บ 14892
Doc: MidpointNormalize example should point to DivergingNorm as a built-in function ยท Issue #14892 ยท matplotlib/matplotlib
July 26, 2019 - Also see matplotlib.colors. # From Joe Kington: This one gives two different linear ramps: 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...
Author: matplotlib
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.1.0 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations_custom.html
Colormap Normalizations Custom โ€” Matplotlib 3.1.0 documentation
May 18, 2019 - # From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.1.1 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations_custom.html
Colormap Normalizations Custom โ€” Matplotlib 3.1.2 documentation
January 5, 2020 - # From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.0.0 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.0.0 documentation
# From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Chris35wills
chris35wills.github.io โ€บ matplotlib_diverging_colorbar
Centre a diverging colorbar at a defined value with matplotlib
August 19, 2016 - elev_min=-1000 elev_max=3000 mid_val=0 plt.imshow(ras, cmap=cmap, clim=(elev_min, elev_max), norm=MidpointNormalize(midpoint=mid_val,vmin=elev_min, vmax=elev_max)) plt.colorbar() plt.show()
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.6.1 โ€บ gallery โ€บ images_contours_and_fields โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.6.1 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(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...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.2.2 โ€บ tutorials โ€บ colors โ€บ colormapnorms.html
Colormap Normalization โ€” Matplotlib 3.2.2 documentation
June 17, 2020 - x, y = [self.vmin, self.vcenter, self.vmax], [0, 0.5, 1] return np.ma.masked_array(np.interp(value, x, y)) fig, ax = plt.subplots() midnorm = MidpointNormalize(vmin=-500., vcenter=0, vmax=4000) pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=midnorm, cmap=terrain_map) ax.set_aspect(1 / np.cos(np.deg2rad(49))) fig.colorbar(pcm, shrink=0.6, extend='both') plt.show()
Find elsewhere
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.4.1 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.4.1 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(vmin, vmax, clip) def __call__(self, value, clip=None): # I'm ignoring masked ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ stable โ€บ gallery โ€บ images_contours_and_fields โ€บ colormap_normalizations.html
Colormap normalizations โ€” Matplotlib 3.11.2 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(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...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 2.1.0 โ€บ tutorials โ€บ colors โ€บ colormapnorms.html
Colormap Normalization โ€” Matplotlib 2.1.0 documentation
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 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...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ devdocs โ€บ gallery โ€บ images_contours_and_fields โ€บ colormap_normalizations.html
Colormap normalizations โ€” Matplotlib 3.12.0.dev272+gfe7830972 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(vmin, vmax, clip) def __call__(self, value, clip=None): # I'm ignoring masked ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.1.0 โ€บ tutorials โ€บ colors โ€บ colormapnorms.html
Colormap Normalization โ€” Matplotlib 3.1.0 documentation
class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, vcenter=None, clip=False): self.vcenter = vcenter 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 # ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 2.1.0 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 2.1.0 documentation
# From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 2.1.2 โ€บ tutorials โ€บ colors โ€บ colormapnorms.html
Colormap Normalization โ€” Matplotlib 2.1.2 documentation
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 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...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.3.3 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.3.3 documentation
# From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.3.1 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.3.1 documentation
# From Joe Kington: This one gives two different linear ramps: 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 ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.7.4 โ€บ gallery โ€บ images_contours_and_fields โ€บ colormap_normalizations.html
Colormap normalizations โ€” Matplotlib 3.7.4 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(vmin, vmax, clip) def __call__(self, value, clip=None): # I'm ignoring masked ...
๐ŸŒ
Matplotlib
matplotlib.org โ€บ 3.5.1 โ€บ gallery โ€บ userdemo โ€บ colormap_normalizations.html
Colormap Normalizations โ€” Matplotlib 3.5.1 documentation
# From Joe Kington: This one gives two different linear ramps: class MidpointNormalize(colors.Normalize): def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False): self.midpoint = midpoint super().__init__(vmin, vmax, clip) def __call__(self, value, clip=None): # I'm ignoring masked ...