Fine-tuning seaborn with Matplotlib

seaborn · Advanced · 12 min

seaborn gets you 90% of the way; the last 10% (labels, layout, legends, highlights) is plain Matplotlib on the objects it returns.

Because seaborn draws with Matplotlib, everything from the Matplotlib lessons applies to its output:

  • An axes-level call returns an Axes: use ax.set(xlabel=..., title=...), tick formatters, ax.annotate, ax.axhline.
  • A figure-level call returns a grid: loop over g.axes.flat, or use g.figure for suptitle and layout.
  • seaborn's legend is a normal Matplotlib legend: ax.get_legend(), sns.move_legend(obj, loc, title=None, ncols=3, frameon=False). Moving it with sns.move_legend keeps the handles that seaborn built, which re-calling ax.legend() can lose.

Combining several seaborn plots in one custom figure is just passing different ax= values from plt.subplots or subplot_mosaic, then calling fig.savefig. layout="constrained" keeps legends and labels from overlapping.

Theme changes go through rcParams, so sns.set_theme affects pure Matplotlib charts too, and Matplotlib style sheets or rc_context affect seaborn. To use seaborn's style only for one figure, use with sns.axes_style("white"):.

seaborn adds artists in a known way: lines in ax.lines, bars in ax.patches, points in ax.collections. You can restyle a specific one afterwards, for example to highlight one bar, though relying on the exact order can break across versions; prefer parameters such as hue or palette when they exist.

Categorical axes place categories at 0, 1, 2 ...; use those positions to annotate.

Example

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib import ticker
import seaborn as sns
import pandas as pd

df = pd.DataFrame({"team": ["data", "web", "ops", "ml"],
                   "share": [0.31, 0.27, 0.12, 0.30]})
fig, ax = plt.subplots(figsize=(5, 3), layout="constrained")
sns.barplot(data=df, y="team", x="share", hue="team", legend=False,
            order=df.sort_values("share", ascending=False)["team"],
            palette={t: "C3" if t == "ml" else "0.7" for t in df.team}, ax=ax)
ax.xaxis.set_major_formatter(ticker.PercentFormatter(xmax=1))
for bars in ax.containers:                  # one container per hue level
    ax.bar_label(bars, fmt="{:.0%}", padding=3)
ax.set(xlabel="Share of incidents", ylabel=None, title="Incidents by team")
sns.despine(left=True)

print([t.get_text() for t in ax.get_yticklabels()])  # ['data', 'ml', 'web', 'ops']
print(len(ax.containers))                            # 4: one per hue level
fig.savefig("incidents.png")

Remember

  • seaborn returns Axes or grids; finish them with ordinary Matplotlib calls
  • Use sns.move_legend instead of rebuilding seaborn legends
  • Compose several seaborn plots in one figure by passing ax=

Watch out for

  • Rebuilding the legend with ax.legend() after a seaborn plot and losing the hue/size handles.

Check yourself

  1. A seaborn catplot legend overlaps the data. What is the most reliable fix?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: seaborn.pydata.org/generated/seaborn.move_legend.html