Axes-level vs figure-level functions

seaborn · Practical · 12 min

Mixing up the two is the top source of seaborn confusion: why ax= is ignored, why figsize does nothing, why the legend sits outside.

seaborn has two kinds of functions.

Axes-level functions (scatterplot, lineplot, histplot, kdeplot, boxplot, barplot, heatmap, regplot ...) draw onto one Matplotlib Axes. They accept ax= and return that Axes, so they compose with any Matplotlib layout you build.

Figure-level functions create their own figure, possibly with many panels, and return a grid object (FacetGrid, PairGrid or JointGrid). There is one per family:

  • relplot (kind "scatter" / "line"),
  • displot ("hist" / "kde" / "ecdf"),
  • catplot ("strip", "box", "violin", "bar", ...),
  • lmplot, pairplot, jointplot.

They add faceting: col="region", row="year", and col_wrap=3 to wrap many columns. They do not accept ax=; size panels with height= (inches per panel) and aspect= (width / height) instead of figsize. The legend is placed outside the panels by default.

The returned grid exposes g.figure, g.axes (2D array), g.axes_dict (by facet value), and helpers: g.set_axis_labels("x", "y"), g.set_titles("{col_name}"), g.set(xlim=...), g.figure.suptitle(...) and g.savefig(...).

Rule of thumb: use figure-level functions to explore and facet quickly; use axes-level functions when placing a chart into a custom Matplotlib layout.

Example

import matplotlib
matplotlib.use("Agg")
import seaborn as sns
import pandas as pd
import numpy as np

rng = np.random.default_rng(3)
df = pd.DataFrame({
    "x": rng.normal(size=300),
    "region": rng.choice(["north", "south", "east", "west"], 300),
    "plan": rng.choice(["free", "pro"], 300),
})
df["y"] = df["x"] * 2 + rng.normal(size=300)

g = sns.relplot(data=df, x="x", y="y", hue="plan", col="region",
                col_wrap=2, height=3, aspect=1.2)
g.set_titles("{col_name}")
g.set_axis_labels("Visits", "Spend")
print(type(g).__name__)                     # FacetGrid
print(g.axes.shape)                         # (4,)  col_wrap flattens the array
print(sorted(g.axes_dict))                  # ['east', 'north', 'south', 'west']
print(g.figure.get_size_inches())          # [8. 6.] (panels plus legend space)
g.savefig("facets.png")

Remember

  • Axes-level functions take ax=; figure-level functions build their own figure
  • Figure-level functions facet with col, row, col_wrap and size with height and aspect
  • Customize grids through g.figure, g.axes, g.set_titles and g.set_axis_labels

Watch out for

  • Passing ax= or setting plt.figure(figsize=...) before relplot; both are ignored by figure-level functions.

Check yourself

  1. You need a KDE plot as the third panel of a hand-built Matplotlib GridSpec layout. Which call fits?

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

Official docs: seaborn.pydata.org/tutorial/function_overview.html