Dataslope logoDataslope

Figure-Level vs. Axes-Level

The one structural idea behind Seaborn's whole API — why some functions facet and return a grid while others draw on a single Axes — and how to choose.

Seaborn has two kinds of plotting functions, and almost every "wait, why doesn't that work?" moment comes from mixing them up. Once you see the split clearly, the entire API organizes itself into a simple map.

This is a foundational page. Spend the time here — it pays off on every later chapter, because each chart type comes in both flavors.

Two kinds of functions

Axes-level functions draw onto a single matplotlib Axes (one set of x/y axes). They behave like ordinary matplotlib commands: they take an ax= argument, draw on it, and return that Axes. Examples: scatterplot, lineplot, histplot, kdeplot, boxplot, violinplot, barplot, countplot, regplot, heatmap.

Figure-level functions manage an entire figure. They create the figure and its axes for you, can split the data into a grid of panels (facets), and return a grid object (FacetGrid, JointGrid, or PairGrid) — not an Axes. Examples: relplot, displot, catplot, lmplot, jointplot, pairplot.

Here's the key relationship: each figure-level function is a wrapper around a family of axes-level functions, and the kind argument picks which one to use.

So sns.relplot(..., kind="scatter") ends up calling scatterplot under the hood, and sns.catplot(..., kind="box") calls boxplot. The figure-level function adds faceting, figure management, and a legend placed outside the plot — on top of the same drawing.

jointplot and pairplot are figure-level too

jointplot and pairplot are also figure-level (they return JointGrid and PairGrid), but they're not simple one-kind wrappers — they assemble several axes-level plots into a fixed layout (a center plot with margins; a matrix of pairwise panels). We give each its own chapter later.

Seeing the difference in what they return

The cleanest way to tell them apart is to look at what comes back. An axes-level function returns a matplotlib Axes:

Code Block
Python 3.13.2

A figure-level function returns a Seaborn grid object:

Code Block
Python 3.13.2

That difference in return type is not a technicality — it determines what you can do next.

What axes-level gives you: composition

Because axes-level functions accept an ax=, you can build your own matplotlib figure and place several different plots into it. Figure-level functions can't do this — they insist on owning the whole figure.

Code Block
Python 3.13.2

This is the axes-level superpower: drop a Seaborn plot into any matplotlib layout, next to anything else.

What figure-level gives you: faceting

Figure-level functions own the figure so they can split it into a grid of panels — one per category — with col and row. This is small multiples, and it's a single keyword:

Code Block
Python 3.13.2

Try doing that with scatterplot and you'd be writing the subplot loop yourself. Faceting is the reason figure-level functions exist.

QuestionSelect one

You want one panel per category (small multiples) using a single Seaborn call with col=. Which kind of function do you need?

An axes-level function like scatterplot.

A figure-level function like relplot.

Either one — col works on both.

Neither — Seaborn can't facet.

Choosing between them

QuestionUse figure-levelUse axes-level
Do I want facets (col/row)?Yes → relplot/displot/catplot
Do I need to place this next to other plots on one figure?Yes → scatterplot, boxplot, ... with ax=
Do I want a clean legend placed outside automatically?Figure-level does thisLegend goes inside the Axes
How do I set the size?height + aspect (per facet)figsize via plt.subplots

A simple rule of thumb:

  • Exploring, or you might want facets? Start with the figure-level function (relplot, displot, catplot).
  • Assembling a custom multi-panel figure, or composing with matplotlib? Use the axes-level function and pass ax=.

Don't try to put a figure-level plot inside a subplot

A common mistake is fig, ax = plt.subplots(); sns.relplot(..., ax=ax). Figure-level functions don't take an ax= — they make their own figure, so you'll get an error or a stray empty Axes. If you need it inside an existing layout, switch to the axes-level version (scatterplot).

Setting size: height/aspect vs figsize

One more practical consequence: figure-level functions are sized with height (inches per facet) and aspect (width = height * aspect), not figsize. Axes-level functions use the matplotlib figure's figsize. Mixing these up is another common source of "why is my plot the wrong size?"

Your turn

Challenge
Python 3.13.2
Facet with a figure-level function

Use a figure-level function to draw a scatter of total_bill (x) vs tip (y) from tips, split into one column per time (Lunch and Dinner). Use sns.relplot with col="time" and assign the result to g.

Because time has two values, you should get a 1-row, 2-column grid of panels.

Check your understanding

QuestionSelect one

What does an axes-level function (e.g. sns.boxplot) return?

A FacetGrid that manages the whole figure.

A matplotlib Axes object that it drew on.

The DataFrame, with a new column added.

Nothing — they only have side effects.

QuestionSelect one

Which call will fail or misbehave?

sns.scatterplot(data=df, x="a", y="b", ax=my_ax)

sns.relplot(data=df, x="a", y="b", col="g")

sns.relplot(data=df, x="a", y="b", ax=my_ax)

sns.boxplot(data=df, x="g", y="a")

QuestionSelect one

sns.catplot(data=tips, x="day", y="total_bill", kind="violin") produces a violin plot. Which axes-level function is doing the actual drawing underneath?

sns.boxplot

sns.violinplot

sns.stripplot

sns.relplot

QuestionSelect one

You're sizing a relplot and it ignores figsize=(10, 4). Why, and what should you use?

relplot has a bug; report it.

Figure-level functions are sized with height and aspect (per facet), not figsize.

You must call plt.figure(figsize=...) first.

figsize only works in dark mode.

You now hold the master key to Seaborn's API: every chart type appears as an axes-level drawer and a figure-level wrapper, and you know when to pick each. From here on we tour the chart families — starting with relational plots, where two variables meet.

On this page