Skip to content

How to draw a diverging image with a zero-centered normalization in Matplotlib (with Code)

The Short Answer (TL;DR)

Pass TwoSlopeNorm(vmin=…, vcenter=0, vmax=…) as the norm of imshow with a diverging colormap. This maps zero to the middle colour even when the negative and positive limits have different magnitudes.

import matplotlib.pyplot as plt; from matplotlib.colors import TwoSlopeNorm
values = [[-2, -1, 0], [1, 2, 6]]
plt.imshow(values, cmap="RdBu_r", norm=TwoSlopeNorm(vmin=-2, vcenter=0, vmax=6))

Detailed Implementation

This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.

python -m pip install numpy==2.5.4 pandas==3.0.6 matplotlib==3.11.2 seaborn==0.13.2
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import TwoSlopeNorm

values = np.array([[-2.0, -1.0, 0.0], [1.0, 2.0, 6.0]])
norm = TwoSlopeNorm(vmin=-2.0, vcenter=0.0, vmax=6.0)
fig, ax = plt.subplots(figsize=(5.5, 3.6), layout="constrained")
image = ax.imshow(values, cmap="RdBu_r", norm=norm, interpolation="nearest")
for (row, col), value in np.ndenumerate(values):
    ax.text(col, row, f"{value:g}", ha="center", va="center", color="black",
            bbox={"facecolor": "white", "alpha": 0.75, "edgecolor": "none"})
fig.colorbar(image, ax=ax, label="Signed deviation", ticks=[-2, 0, 6])
ax.set(xlabel="Column", ylabel="Row", title="Zero at the colormap midpoint")
ax.set_xticks([0, 1, 2])
ax.set_yticks([0, 1])
fig.savefig("zero-centered-image.png", dpi=160)
plt.show()

Zero uses the centre of the diverging colormap. The endpoints -2 and 6 use opposite ends, and the colorbar labels make the asymmetric numerical range explicit.

Why does an ordinary linear scale place zero elsewhere?

For limits -2 and 6, ordinary min-max normalization places zero at 0.25. TwoSlopeNorm instead reserves half of the colour range for values below zero and half for values above it.

Are equal-magnitude deviations equally far from the midpoint?

Not with asymmetric limits. Here -1 maps to 0.25 and +1 maps to about 0.5833. Choose symmetric limits when equal positive and negative magnitudes should have equal colour distances.

How should several panels be compared?

Use the same normalization object and colormap for panels that need comparable colours. Set ordered limits with vmin < vcenter < vmax; describe what zero represents in the underlying measurement.

Mathematical Context

Within the chosen bounds, normalization is linear on each side of zero, with a different slope on each side. For vmin=-2 and vmax=6, negative values have normalized coordinate (v+2)/4, while nonnegative values have coordinate 1/2+v/12. This visual transformation changes colour allocation and leaves the measurements unchanged.

\[ z(v)=(v+2)/4\ \mathrm{if}\ v\leq0;\quad z(v)=1/2+v/12\ \mathrm{if}\ v>0 \]

Reproducibility

Executed with Python 3.12.14; numpy 2.5.4, pandas 3.0.6, matplotlib 3.11.2, seaborn 0.13.2. Automated checks confirm the stated output properties.

Primary documentation

Related guides