pythonforbio.
[WASM idle]
Plotting13/14

seaborn Themes and Palettes

Starting sandbox…
Beginnerlesson

seaborn Themes and Palettes

In one line: sns.set_theme() once at the top of a script styles every plot — including your raw matplotlib ones.

set_theme

sns.set_theme(
    style="whitegrid",       # darkgrid | whitegrid | dark | white | ticks
    context="paper",         # paper | notebook | talk | poster
    palette="colorblind",
    font="Arial",
    font_scale=1.0,
    rc={"figure.figsize": (5, 3.5), "pdf.fonttype": 42},
)

This writes into matplotlib's rcParams, so it affects everything, not just seaborn calls. See rcParams and Style Sheets.

Styles

style Look
darkgrid grey background, white grid (seaborn's default)
whitegrid white background, grey grid
dark grey background, no grid
white white background, no grid
ticks white, no grid, tick marks — closest to journal convention

For publication, ticks or whitegrid with sns.despine():

sns.set_theme(style="ticks")
sns.despine()                                  # remove top and right spines
sns.despine(trim=True, offset=5)               # trim to data range, detach
sns.despine(fig=fig, left=True)                # whole figure

offset=5 detaching the axes from the plot area is a small touch that reads as deliberate design.

Context

sns.set_context("paper")     # smallest — journal figures
sns.set_context("notebook")  # default
sns.set_context("talk")      # slides
sns.set_context("poster")    # largest
sns.set_context("talk", font_scale=1.2, rc={"lines.linewidth": 2.5})

Context scales all font sizes, line widths and marker sizes together. Converting a paper figure to a talk figure is one changed word, not fifteen changed numbers.

Palettes

sns.color_palette()                        # current default
sns.color_palette("colorblind", 8)
sns.color_palette("deep")                  # seaborn's default
sns.color_palette("muted"); sns.color_palette("pastel")
sns.color_palette("Set2", 6)               # ColorBrewer qualitative
sns.color_palette("husl", 8)               # evenly spaced hues

sns.color_palette("rocket", as_cmap=True)      # sequential
sns.color_palette("mako", as_cmap=True)
sns.cubehelix_palette(8, start=.5, rot=-.75)   # greyscale-safe sequential
sns.diverging_palette(240, 10, as_cmap=True)   # custom diverging
sns.light_palette("seagreen", as_cmap=True)
sns.dark_palette("#69d", reverse=True)

sns.set_palette("colorblind")                   # set globally
sns.color_palette("colorblind")                 # display in a notebook

rocket, mako, flare, crest, vlag, icefire are seaborn's own perceptually uniform maps and are all good defaults. vlag is the diverging one you want for correlation and z-score heatmaps.

Explicit category colours

The habit that makes 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)
sns.lineplot(data=ts, x="t", y="v", hue="condition", palette=CONDITION_COLORS)

Without an explicit mapping, seaborn assigns colours in the order categories appear in each subset — so "treated" can be blue in panel A and orange in panel B if panel B happens to lack the control group. Define the dict once. See Colormaps and Color.

Category order

sns.boxplot(data=long, x="condition", y="expr",
            order=["control", "low", "high"],
            hue_order=["male", "female"])

Or better, set it once with an ordered categorical, which fixes plot order and sort order and groupby order simultaneously:

long["condition"] = pd.Categorical(
    long["condition"], categories=["control", "low", "high"], ordered=True
)

See Categorical dtype.

hue with continuous data

sns.scatterplot(data=df, x="a", y="b", hue="expression",
                palette="viridis", hue_norm=(0, 10))

seaborn produces a legend of sample values rather than a colorbar. For a proper colorbar, add one manually:

fig, ax = plt.subplots()
sc = ax.scatter(df["a"], df["b"], c=df["expression"], cmap="viridis", s=10)
fig.colorbar(sc, ax=ax, label="log$_2$ CPM")

Raw matplotlib is the better tool for continuous colour encoding.

Temporary changes

with sns.axes_style("white"):
    fig, ax = plt.subplots()
    ...

with sns.plotting_context("talk"):
    ...

with sns.color_palette("Set2"):
    ...

Use these in library code rather than calling set_theme() globally — you should not silently restyle your users' plots.

Inspecting and resetting

sns.axes_style()               # current style dict
sns.plotting_context()         # current context dict
sns.reset_defaults()           # back to matplotlib defaults
sns.reset_orig()               # back to matplotlib's original rcParams

A project header

import matplotlib as mpl, matplotlib.pyplot as plt, seaborn as sns

sns.set_theme(style="ticks", context="paper", palette="colorblind",
              font="Arial", rc={"figure.figsize": (3.5, 2.6)})
mpl.rcParams.update({
    "pdf.fonttype": 42, "ps.fonttype": 42, "svg.fonttype": "none",
    "savefig.bbox": "tight", "savefig.dpi": 300,
    "axes.titlelocation": "left",
    "font.sans-serif": ["Arial", "Helvetica", "DejaVu Sans"],
})

CONDITION_COLORS = {"control": "#4C72B0", "treated": "#DD8452"}
CHROM_ORDER = [f"chr{i}" for i in range(1, 23)] + ["chrX", "chrY", "chrM"]

Put this in a plotstyle.py and import it everywhere in the project. Every figure then matches, and switching the whole paper to a different palette is a one-line change.

Bioinformatics conventions

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

# raw counts heatmap: sequential
sns.heatmap(cpm, cmap="rocket", norm=LogNorm())

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

# categorical sample groups
sns.set_palette("colorblind")

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

# significance: grey bulk, one accent
sns.scatterplot(data=de, x="log2fc", y="neglog10p", hue="sig",
                palette={True: "crimson", False: "lightgrey"},
                hue_order=[False, True])

hue_order=[False, True] in the last example matters: it draws the grey non-significant points first, so the crimson hits land on top rather than being buried.

Common mistakes

  • sns.set() — deprecated alias for set_theme().
  • Inconsistent category colours across panels.
  • Not setting context="paper" for journal figures, so the fonts are notebook-sized.
  • darkgrid in a publication. Grey backgrounds waste ink and reproduce badly.
  • Global set_theme() in a library.
  • Continuous hue producing a sample legend instead of a colorbar.
  • Assuming Arial is installed. Provide fallbacks.
  • Forgetting sns.despine() after choosing style="ticks".

See also

Colormaps and Color · rcParams and Style Sheets · Seaborn · Categorical dtype · Saving Figures · Faceting

scratch

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