Seaborn Themes and Palettes
seaborn Themes and Palettes
Core Principle: Seaborn theme configurations establish global graphic aesthetics, tick layouts, font scalings, and color palettes across both Seaborn and direct Matplotlib figures.
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 forset_theme().- Inconsistent category colours across panels.
- Not setting
context="paper"for journal figures, so the fonts are notebook-sized. darkgridin a publication. Grey backgrounds waste ink and reproduce badly.- Global
set_theme()in a library. - Continuous
hueproducing a sample legend instead of a colorbar. - Assuming Arial is installed. Provide fallbacks.
- Forgetting
sns.despine()after choosingstyle="ticks".
See also
Colormaps and Color · rcParams and Style Sheets · Seaborn · Categorical dtype · Saving Figures · Faceting
Exercise
seaborn is not available in this sandbox, so derive the palette values yourself
from a matplotlib colormap. Write palette_map(categories, subset_a, subset_b).
The first input line is the canonical category order; the second and third are
two subsets of it, standing in for two panels of one figure. Sample the
viridis colormap evenly over the canonical order: category i of n takes
the position i / (n - 1), or 0.0 when n is 1: and convert each colour
with matplotlib.colors.to_hex. Build that mapping once from the canonical
order and look both subsets up in it; a mapping rebuilt from each subset's own
categories is what makes a condition blue in one panel and orange in the next.
Return four lines: palette= followed by category=#hex for every canonical
category in order, space separated; A: and B: followed by the same
rendering for each subset, or empty when a subset has no categories, with any
category absent from the canonical list rendered as unmapped; and
shared=<n> consistent=<bool>, counting the categories the two subsets have in
common and reporting whether every category either subset names was found in
the mapping. Return the single line none when the canonical list is empty.
Test cases · 4
| # | via | input | expected stdout |
|---|---|---|---|
| 1 | stdin | control low high control low high low high | palette=control=#440154 low=#21918c high=#fde725 A: control=#440154 low=#21918c high=#fde725 B: low=#21918c high=#fde725 shared=2 consistent=True |
| 2 | stdin | control treated drug vehicle control drug treated vehicle | palette=control=#440154 treated=#31688e drug=#35b779 vehicle=#fde725 A: control=#440154 drug=#35b779 B: treated=#31688e vehicle=#fde725 shared=0 consistent=True |
| 3 | stdin | only only only | palette=only=#440154 A: only=#440154 B: only=#440154 shared=1 consistent=True |
| 4 | stdin | a b c a b d | palette=a=#440154 b=#21918c c=#fde725 A: a=#440154 b=#21918c B: d=unmapped shared=0 consistent=False |
Hints · 3
01Hint
A colormap is callable: `cmap(0.5)` returns an RGBA tuple you can pass to `to_hex`.
02Hint
With one category there is no interval to divide, so guard `i / (n - 1)` against a zero denominator.
03Hint
Look subsets up in the canonical mapping; never re-derive positions from a subset's own length.