Skip to content

Colormap Selection

Choosing the right colormap is critical for effective data visualization. This guide covers colormap categories, selection criteria, and best practices.

Mental Model

Match your colormap to your data type: sequential (e.g., viridis) for ordered values like temperature, diverging (e.g., coolwarm) for values centered around zero like profit/loss, and qualitative (e.g., Set1) for unordered categories. A wrong choice can mislead readers -- the default viridis is perceptually uniform and colorblind-safe, making it a reliable default.

Default Strategy

Use viridis unless you have a specific reason not to. It is perceptually uniform (equal steps in data produce equal perceived color changes), prints well in grayscale, and is accessible to colorblind readers. Switch to a diverging map (coolwarm, RdBu) only when your data has a meaningful center, and to qualitative (Set1, tab10) only for unordered categories.

Colormaps Shape Perception

Colormaps do not just display data — they distort it. A poor colormap can create fake patterns, hide real gradients, or exaggerate small differences:

  • jet creates false boundaries at yellow-green and cyan-blue transitions where the brightness changes non-monotonically — features appear where none exist
  • hot compresses the dark end, hiding low values
  • Diverging maps on non-centered data imply a midpoint that doesn't exist

Choosing a colormap is choosing how differences are perceived.

Why Jet and Rainbow Are Harmful

Human vision perceives brightness non-uniformly — we are far more sensitive to changes in yellow-green than in blue or red. Non-uniform colormaps like jet create false boundaries and hide real gradients. A smooth gradient in data can appear as sharp bands in jet, leading readers to see structure that does not exist. Perceptually uniform colormaps (viridis, plasma, inferno) eliminate this distortion.

Colormap Categories

Matplotlib provides several categories of colormaps:

Category Use Case Examples
Sequential Data with natural ordering (low→high) viridis, plasma, Blues
Diverging Data with meaningful center point coolwarm, RdBu, seismic
Cyclic Periodic data (angles, phases) twilight, hsv
Qualitative Categorical data Set1, tab10, Paired

Setup

```python import matplotlib.pyplot as plt import numpy as np

x = np.linspace(-3, 3, 100) y = np.linspace(-3, 3, 100) X, Y = np.meshgrid(x, y) ```


Sequential Colormaps

Best for data that progresses from low to high values.

Perceptually Uniform

These colormaps have uniform perceptual brightness changes, making them ideal for scientific visualization.

```python Z = np.exp(-(X2 + Y2))

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['viridis', 'plasma', 'inferno', 'magma', 'cividis', 'turbo']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Perceptually Uniform Sequential Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

Single-Hue Sequential

```python fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['Blues', 'Greens', 'Reds', 'Purples', 'Oranges', 'Greys']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Single-Hue Sequential Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

Multi-Hue Sequential

```python fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['YlOrRd', 'YlGnBu', 'BuPu', 'GnBu', 'PuBuGn', 'OrRd']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Multi-Hue Sequential Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

When to Use Sequential

  • Density plots
  • Probability distributions
  • Temperature (single direction)
  • Elevation maps
  • Any monotonically increasing data

```python

Example: Probability density

from scipy import stats

rv = stats.multivariate_normal([0, 0], [[1, 0], [0, 1]]) Z_pdf = rv.pdf(np.dstack((X, Y)))

fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(Z_pdf, extent=[-3, 3, -3, 3], origin='lower', cmap='viridis') ax.set_title('Bivariate Normal PDF') ax.set_xlabel('x') ax.set_ylabel('y') plt.colorbar(im, ax=ax, label='Density') plt.show() ```


Diverging Colormaps

Best for data with a meaningful center point (often zero).

Standard Diverging

```python Z_div = X2 - Y2 # Saddle function with positive and negative values

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['coolwarm', 'RdBu', 'seismic', 'bwr', 'RdYlBu', 'PiYG']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z_div, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Diverging Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

Centered Normalization

For diverging data, center the colormap at zero:

```python from matplotlib.colors import TwoSlopeNorm

Z_asym = X2 - Y2 + 2 # Asymmetric around zero

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

Without centering

im1 = axes[0].imshow(Z_asym, cmap='RdBu', origin='lower') axes[0].set_title('Default Normalization') plt.colorbar(im1, ax=axes[0])

With centering at zero

norm = TwoSlopeNorm(vmin=Z_asym.min(), vcenter=0, vmax=Z_asym.max()) im2 = axes[1].imshow(Z_asym, cmap='RdBu', norm=norm, origin='lower') axes[1].set_title('Centered at Zero (TwoSlopeNorm)') plt.colorbar(im2, ax=axes[1])

plt.tight_layout() plt.show() ```

When to Use Diverging

  • Correlation matrices
  • Temperature anomalies
  • Profit/loss data
  • Deviations from mean
  • Any data with positive and negative values around a center

```python

Example: Correlation matrix

np.random.seed(42) data = np.random.randn(5, 100) corr = np.corrcoef(data)

fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(corr, cmap='RdBu', vmin=-1, vmax=1) ax.set_title('Correlation Matrix') ax.set_xticks(range(5)) ax.set_yticks(range(5)) plt.colorbar(im, ax=ax, label='Correlation') plt.show() ```


Cyclic Colormaps

Best for periodic data like angles or phases.

Available Cyclic Colormaps

```python theta = np.arctan2(Y, X) # Angle in radians

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

cmaps = ['twilight', 'twilight_shifted', 'hsv']

for ax, cmap in zip(axes, cmaps): im = ax.imshow(theta, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Cyclic Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

When to Use Cyclic

  • Angular data (directions, phases)
  • Time of day (circular)
  • Hue values
  • Any data that wraps around

```python

Example: Phase angle

Z_complex = X + 1j * Y phase = np.angle(Z_complex)

fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(phase, extent=[-3, 3, -3, 3], origin='lower', cmap='twilight') ax.set_title('Complex Phase Angle') ax.set_xlabel('Re(z)') ax.set_ylabel('Im(z)') plt.colorbar(im, ax=ax, label='Phase (radians)') plt.show() ```


Qualitative Colormaps

Best for categorical data with no natural ordering.

Available Qualitative Colormaps

```python fig, axes = plt.subplots(2, 3, figsize=(15, 8))

cmaps = ['Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'Paired']

Create categorical-like data

np.random.seed(42) Z_cat = np.random.randint(0, 8, (20, 20))

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z_cat, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}'") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Qualitative Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

When to Use Qualitative

  • Segmentation maps
  • Cluster labels
  • Land use categories
  • Any discrete, unordered categories

```python

Example: Cluster visualization

from sklearn.datasets import make_blobs from sklearn.cluster import KMeans

X_data, _ = make_blobs(n_samples=300, centers=4, random_state=42) kmeans = KMeans(n_clusters=4, random_state=42).fit(X_data)

fig, ax = plt.subplots(figsize=(8, 6)) scatter = ax.scatter(X_data[:, 0], X_data[:, 1], c=kmeans.labels_, cmap='Set1') ax.set_title('K-Means Clustering') plt.colorbar(scatter, ax=ax, label='Cluster') plt.show() ```


Reversed Colormaps

Add _r suffix to reverse any colormap.

```python fig, axes = plt.subplots(2, 3, figsize=(15, 9))

Z = np.exp(-(X2 + Y2))

pairs = [ ('viridis', 'viridis_r'), ('Blues', 'Blues_r'), ('coolwarm', 'coolwarm_r') ]

for i, (cmap1, cmap2) in enumerate(pairs): im1 = axes[0, i].imshow(Z, cmap=cmap1, origin='lower') axes[0, i].set_title(f"'{cmap1}'") axes[0, i].axis('off') plt.colorbar(im1, ax=axes[0, i], shrink=0.8)

im2 = axes[1, i].imshow(Z, cmap=cmap2, origin='lower')
axes[1, i].set_title(f"'{cmap2}'")
axes[1, i].axis('off')
plt.colorbar(im2, ax=axes[1, i], shrink=0.8)

plt.suptitle('Original vs Reversed Colormaps', fontsize=14) plt.tight_layout() plt.show() ```


Color Vision Deficiency

Consider colorblind-friendly options.

Colorblind-Safe Colormaps

```python fig, axes = plt.subplots(2, 3, figsize=(15, 9))

Colorblind-friendly options

cmaps = ['viridis', 'plasma', 'cividis', 'inferno', 'magma', 'YlOrBr']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(Z, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}' (colorblind-safe)") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Colorblind-Friendly Colormaps', fontsize=14) plt.tight_layout() plt.show() ```

Avoid These for Colorblind Users

  • jet (rainbow) - poor perceptual uniformity
  • hsv - hard to distinguish regions
  • rainbow - misleading intensity perception

```python fig, axes = plt.subplots(1, 3, figsize=(15, 4))

Problematic colormaps

cmaps = ['jet', 'rainbow', 'hsv']

for ax, cmap in zip(axes, cmaps): im = ax.imshow(Z, cmap=cmap, origin='lower') ax.set_title(f"'{cmap}' (avoid for scientific use)") ax.axis('off') plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Colormaps to Avoid', fontsize=14) plt.tight_layout() plt.show() ```


Custom Colormaps

Create custom colormaps for specific needs.

From Listed Colors

```python from matplotlib.colors import ListedColormap

colors = ['#2ecc71', '#3498db', '#9b59b6', '#e74c3c', '#f39c12'] custom_cmap = ListedColormap(colors)

fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(np.random.randint(0, 5, (20, 20)), cmap=custom_cmap) ax.set_title('Custom ListedColormap') plt.colorbar(im, ax=ax) plt.show() ```

Linear Segmented

```python from matplotlib.colors import LinearSegmentedColormap

White to blue gradient

colors = ['white', 'lightblue', 'blue', 'darkblue'] custom_cmap = LinearSegmentedColormap.from_list('custom_blues', colors, N=256)

fig, ax = plt.subplots(figsize=(8, 6)) im = ax.imshow(Z, cmap=custom_cmap, origin='lower') ax.set_title('Custom LinearSegmentedColormap') plt.colorbar(im, ax=ax) plt.show() ```

Truncated Colormap

```python def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=256): new_cmap = LinearSegmentedColormap.from_list( f'trunc({cmap.name},{minval:.2f},{maxval:.2f})', cmap(np.linspace(minval, maxval, n)) ) return new_cmap

Use only middle portion of viridis

cmap_full = plt.cm.viridis cmap_trunc = truncate_colormap(cmap_full, 0.2, 0.8)

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

im1 = axes[0].imshow(Z, cmap=cmap_full, origin='lower') axes[0].set_title('Full viridis') plt.colorbar(im1, ax=axes[0])

im2 = axes[1].imshow(Z, cmap=cmap_trunc, origin='lower') axes[1].set_title('Truncated viridis (0.2-0.8)') plt.colorbar(im2, ax=axes[1])

plt.tight_layout() plt.show() ```


Selection Decision Tree

Is your data... │ ├── Ordered/Sequential (low → high)? │ ├── Need colorblind-safe? → viridis, cividis, plasma │ ├── Single hue preferred? → Blues, Greens, Greys │ └── Multi-hue preferred? → YlOrRd, YlGnBu │ ├── Diverging (center is meaningful)? │ ├── Center at zero? → coolwarm, RdBu, seismic │ └── Asymmetric data? → Use TwoSlopeNorm │ ├── Cyclic (wraps around)? │ └── Use twilight or twilight_shifted │ └── Categorical (no order)? ├── Few categories (<10)? → Set1, tab10 └── Many categories? → tab20, Set3


Quick Reference Table

Data Type Recommended Avoid
Density/PDF viridis, plasma jet, rainbow
Elevation terrain, gist_earth hsv
Temperature coolwarm, RdBu jet
Correlation RdBu, coolwarm sequential
Binary Greys, binary diverging
Categories Set1, tab10 sequential
Phase/Angle twilight, hsv sequential
Heatmap viridis, YlOrRd jet

List All Available Colormaps

```python import matplotlib.pyplot as plt

cmaps = plt.colormaps() print(f"Total colormaps: {len(cmaps)}") print("\nFirst 20:", cmaps[:20]) ```

Visual Catalog

```python import matplotlib.pyplot as plt import numpy as np

gradient = np.linspace(0, 1, 256).reshape(1, -1)

categories = { 'Perceptually Uniform': ['viridis', 'plasma', 'inferno', 'magma', 'cividis'], 'Sequential': ['Blues', 'Greens', 'Reds', 'YlOrRd', 'PuBuGn'], 'Diverging': ['coolwarm', 'RdBu', 'seismic', 'bwr', 'RdYlBu'], 'Cyclic': ['twilight', 'twilight_shifted', 'hsv'], }

for category, cmap_list in categories.items(): fig, axes = plt.subplots(len(cmap_list), 1, figsize=(8, len(cmap_list) * 0.4)) fig.suptitle(category, fontsize=12, fontweight='bold')

for ax, cmap in zip(axes, cmap_list):
    ax.imshow(gradient, aspect='auto', cmap=cmap)
    ax.set_ylabel(cmap, rotation=0, ha='right', va='center', fontsize=9)
    ax.set_xticks([])
    ax.set_yticks([])

plt.tight_layout()
plt.show()

```


Exercises

Exercise 1. Write code that displays the same 8x8 random data array using four different colormaps in a 2x2 subplot grid. Add the colormap name as the title of each subplot.

Solution to Exercise 1

```python import matplotlib.pyplot as plt import numpy as np

np.random.seed(42) data = np.random.randn(8, 8)

fig, axes = plt.subplots(2, 2, figsize=(10, 8)) cmaps = ['viridis', 'plasma', 'coolwarm', 'gray']

for ax, cmap in zip(axes.flat, cmaps): im = ax.imshow(data, cmap=cmap) ax.set_title(f"cmap='{cmap}'") fig.colorbar(im, ax=ax, shrink=0.8)

plt.tight_layout() plt.show() ```


Exercise 2. Explain the difference between sequential, diverging, and qualitative colormaps. Give one example of each and when to use it.

Solution to Exercise 2
  • Sequential colormaps (e.g., 'viridis', 'Blues') map low-to-high values with a single color gradient. Use for data that goes in one direction (counts, temperatures, concentrations).
  • Diverging colormaps (e.g., 'coolwarm', 'RdBu') have two contrasting colors meeting at a midpoint. Use for data with a meaningful center point (e.g., deviations from zero, anomalies).
  • Qualitative colormaps (e.g., 'Set1', 'tab10') use distinct colors without implied ordering. Use for categorical data or distinguishing groups.

Exercise 3. Write code that creates a custom diverging colormap centered at zero using matplotlib.colors.TwoSlopeNorm. Apply it to data that ranges from -5 to 10.

Solution to Exercise 3

```python import matplotlib.pyplot as plt import matplotlib.colors as mcolors import numpy as np

np.random.seed(42) data = np.random.uniform(-5, 10, (10, 10))

norm = mcolors.TwoSlopeNorm(vmin=-5, vcenter=0, vmax=10)

fig, ax = plt.subplots() im = ax.imshow(data, cmap='RdBu_r', norm=norm) fig.colorbar(im, ax=ax, label='Value') ax.set_title('Diverging Colormap Centered at Zero') plt.show() ```


Exercise 4. Create a figure showing how vmin and vmax affect the colormap mapping. Show the same data with default limits and with vmin=-2, vmax=2.

Solution to Exercise 4

```python import matplotlib.pyplot as plt import numpy as np

np.random.seed(42) data = np.random.randn(8, 8) * 3

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))

im1 = ax1.imshow(data, cmap='RdBu') ax1.set_title('Default vmin/vmax') fig.colorbar(im1, ax=ax1)

im2 = ax2.imshow(data, cmap='RdBu', vmin=-2, vmax=2) ax2.set_title('vmin=-2, vmax=2') fig.colorbar(im2, ax=ax2)

plt.tight_layout() plt.show() ```