pythonforbio.
[WASM idle]
Plotting02/14

Colormaps and Color

Starting sandbox…
Beginnerlesson

Colormaps and Color

In one line: match the colormap type to the data type, never use jet, and check it works in greyscale and for colourblind readers.

The three colormap types

Type Use for Examples
Sequential ordered data from a baseline: counts, coverage, expression, density viridis, magma, Blues, rocket
Diverging signed data with a meaningful midpoint: log fold change, correlation, z-score vlag, RdBu_r, coolwarm, icefire
Qualitative unordered categories: sample groups, chromosomes, cell types tab10, Set2, colorblind

Using the wrong type is a real error, not a style preference. A sequential colormap on a log fold change puts its midpoint at an arbitrary value; a qualitative palette on ordered data destroys the ordering.

Always center diverging maps

sns.heatmap(logfc_matrix, cmap="vlag", center=0)
sns.heatmap(corr, cmap="vlag", center=0, vmin=-1, vmax=1)

from matplotlib.colors import TwoSlopeNorm
ax.imshow(m, cmap="RdBu_r", norm=TwoSlopeNorm(vcenter=0, vmin=-3, vmax=5))

Without center=0, if your data runs −1 to +5 the colormap's white point lands at +2 and every genuinely unchanged gene is drawn as up-regulated. This is one of the most common quietly-misleading things in genomics figures.

Why not jet / rainbow

The jet colormap has three documented problems:

  1. Perceptually non-uniform. Equal steps in data are not equal steps in apparent colour. Its sharp cyan/yellow bands create visual edges where the data is smooth — inventing boundaries that do not exist.
  2. Not monotonic in lightness. Printed in greyscale it becomes unreadable — the yellow and cyan map to the same grey.
  3. Bad for colour vision deficiency. Roughly 8% of men with northern European ancestry have red-green CVD; jet's red and green extremes are its most important values.

viridis was designed to fix all three: perceptually uniform, monotonically increasing in lightness, and CVD-safe. It is the default for a reason.

Colourblind-safe choices

sns.set_palette("colorblind")
sns.color_palette("colorblind")           # 10 distinguishable colours

# sequential and diverging maps that are already safe
"viridis", "cividis", "magma", "rocket"   # sequential
"vlag", "icefire", "RdBu_r"               # diverging

cividis is specifically designed to appear near-identical to viewers with and without CVD.

Never encode information in colour alone. Redundantly encode with marker shape, line style, direct labels, or position:

sns.scatterplot(data=df, x="pc1", y="pc2", hue="batch", style="condition")
ax.plot(x, y1, c="C0", ls="-",  marker="o", label="control")
ax.plot(x, y2, c="C1", ls="--", marker="s", label="treated")

Check your figure: convert it to greyscale, or use a CVD simulator (daltonlens, or the Coblis web tool). If two series become indistinguishable, fix it before submitting.

Specifying colours

c="crimson"                  # named
c="#4C72B0"                  # hex
c=(0.3, 0.5, 0.7)            # RGB floats
c=(0.3, 0.5, 0.7, 0.5)       # RGBA
c="C0"                        # the current cycle's first colour
c="0.5"                       # greyscale string

Using "C0", "C1", ... rather than literal colours means your plots automatically follow whatever palette you set with sns.set_theme(). Change the theme once, every figure updates.

Palettes

sns.color_palette()                              # current default
sns.color_palette("colorblind", 8)
sns.color_palette("Set2", 5)
sns.color_palette("viridis", as_cmap=True)
sns.cubehelix_palette(8, start=.5, rot=-.75)     # sequential, greyscale-safe
sns.diverging_palette(240, 10, as_cmap=True)
sns.light_palette("seagreen", as_cmap=True)

sns.set_palette("colorblind")                     # set globally

Explicit category → colour mapping is the habit that keeps a multi-panel figure coherent:

CONDITION_COLORS = {"control": "#4C72B0", "treated": "#DD8452", "drug": "#55A868"}
sns.scatterplot(data=df, x="a", y="b", hue="condition", palette=CONDITION_COLORS)
sns.boxplot(data=long, x="condition", y="expr", palette=CONDITION_COLORS)

Without this, seaborn assigns colours in the order categories appear — so "treated" can be blue in panel A and orange in panel B. Define the dict once at the top of your script.

Normalization

from matplotlib.colors import LogNorm, TwoSlopeNorm, BoundaryNorm

ax.imshow(counts, norm=LogNorm(vmin=1, vmax=1e4), cmap="viridis")
ax.imshow(logfc, norm=TwoSlopeNorm(vcenter=0), cmap="RdBu_r")
ax.imshow(m, norm=BoundaryNorm([0, 1, 5, 10, 50], ncolors=256), cmap="viridis")

ax.imshow(m, vmin=0, vmax=10)                    # clip the range
sns.heatmap(m, robust=True)                       # use 2nd/98th percentiles

LogNorm for count data. Expression and coverage span orders of magnitude; on a linear colour scale one outlier makes everything else uniformly dark.

robust=True in seaborn clips to the 2nd–98th percentile, which stops a single extreme value from consuming the entire colour range. Extremely useful, and worth disclosing in the caption.

Colorbars

im = ax.imshow(m, cmap="viridis")
cb = fig.colorbar(im, ax=ax, label="log$_2$ CPM", shrink=0.8)
cb.ax.tick_params(labelsize=8)
cb.set_ticks([0, 5, 10])

Always label the colorbar. An unlabelled colour scale is an unlabelled axis.

Transparency for overplotting

ax.scatter(x, y, alpha=0.1, s=2, rasterized=True)

Alpha stops helping past roughly 50,000 points — everything saturates to solid. At that scale use hexbin or a 2-D histogram. See Common Plot Types.

Bioinformatics conventions

# expression heatmaps: z-scored per gene, diverging, centred
sns.clustermap(mat, z_score=0, cmap="vlag", center=0)

# raw expression: sequential, log-normed
sns.heatmap(cpm, cmap="rocket", norm=LogNorm())

# correlation: diverging, full [-1, 1]
sns.heatmap(corr, cmap="vlag", center=0, vmin=-1, vmax=1, square=True)

# volcano: grey background, one accent for hits
colors = np.where(sig, "crimson", "lightgrey")

# Manhattan: two alternating neutral colours by chromosome
alt = ["#4C72B0", "#A9BCD8"]

# nucleotides: a widely used convention
BASE_COLORS = {"A": "#3DA853", "C": "#4285F4", "G": "#F9AB00", "T": "#EA4335"}

Grey for the bulk, colour for the point. A volcano plot where all 20,000 genes are coloured is 20,000 things competing for attention. Grey out the non-significant ones and the eye goes exactly where you want it.

Common mistakes

  • jet / rainbow.
  • Diverging colormap without center=.
  • Sequential colormap on signed data.
  • Qualitative palette on ordered data.
  • Colour as the only encoding.
  • Inconsistent category colours across panels of one figure.
  • Unlabelled colorbar.
  • Linear colour scale on count data spanning orders of magnitude.
  • Too many categories — beyond ~8, colours stop being distinguishable. Group, facet, or label directly.
  • Never checking in greyscale.

See also

Common Plot Types · seaborn Themes and Palettes · Applied - Heatmaps and Clustermaps · Axes Styling and Annotation · Saving Figures

lesson example

No output yet — run the code to populate this drawer.