Facets and Grids
Small multiples — splitting data by category into a grid of panels with col and row, the FacetGrid workflow underneath, and when a grid becomes too much.
One of the most powerful moves in all of visualization is the small multiple: instead of cramming every group onto one busy chart, draw the same chart many times — once per group — and lay the copies out in a grid. The eye compares panels effortlessly, because only the data changes from cell to cell, never the encoding.
Seaborn calls this faceting, and it's built into every figure-level
function through two keywords: col and row.
Faceting is just col and row
We've used col already. Add row and you get a 2-D grid — one panel for
every combination of the two faceting variables:
Each panel holds the same scatter plot for its slice of the data, and — crucially — every panel shares the same axes by default, so a point in one cell is directly comparable to a point in another. That shared scale is what makes small multiples honest.
hue overlays; col/row separate
hue draws groups on top of each other in one panel (good for direct
overlap and a few groups). col/row give each group its own panel (good
for many groups, or when overlap would be a mess). You can combine them:
color by hue and split by col. Choosing between overlay and separation
is one of the everyday judgment calls of faceting.
Wrapping many categories with col_wrap
A single faceting variable with many levels would stretch into one very wide
row. col_wrap wraps the panels after N columns into a tidy block:
The FacetGrid workflow underneath
The figure-level functions do their faceting through a class called
FacetGrid. You rarely call it directly, but understanding its
three-step workflow demystifies what relplot and friends are doing — and
lets you build custom grids when you need them.
In code, those steps look like this:
That is exactly the machinery sns.relplot(..., col="time", hue="sex") runs
for you. Reach for an explicit FacetGrid only when you want to map a custom
function or layer several plots per panel; otherwise let the figure-level
function do it.
Three grid classes, one idea
Seaborn has three grid classes: FacetGrid (same plot across category
panels — this page), PairGrid (every variable against every other —
the pair-plots chapter), and JointGrid (one center plot with marginal
distributions — the joint-plots chapter). All three follow the same
"set up the grid, then map plots onto it" pattern.
Shared scales — usually keep them
By default facets share x- and y-limits. That's almost always what you want,
because it keeps panels comparable. Occasionally one group's range dwarfs the
others and you'll want to free a scale with sharey=False (via
facet_kws={"sharey": False} on the figure-level functions) — but do it
consciously, and tell your reader, because un-shared axes make panels look
similar when they aren't.
Why does Seaborn make facets share the same axis limits by default?
To make the figure render faster.
So panels are directly comparable — a position in one panel means the same as the same position in another.
Because Seaborn cannot compute separate limits per panel.
To save vertical space.
When a grid becomes too much
Faceting multiplies panels: col levels × row levels. That's its
weakness as well as its strength.
- Too many facets. Twenty tiny panels are as hard to read as one
overcrowded chart. Keep a facet variable to a handful of levels; use
col_wrapto keep the block compact. - A high-cardinality facet variable. Faceting on something with 50+ categories produces 50+ unreadable thumbnails. Group rare categories together, filter to the ones you care about, or pick a different view.
- Sparse cells. If some category combinations have almost no data, those panels are nearly empty and waste space — consider a single faceting dimension instead of two.
Facet count = col × row
Before adding a row, do the multiplication. A col with 4 levels and a
row with 5 is already 20 panels. Small multiples are a scalpel, not a
firehose — a few well-chosen facets beat a wall of thumbnails.
Your turn
Using tips, draw a scatter of total_bill (x) vs tip (y) with
sns.relplot, faceted into a grid with:
- one column per
time(Lunch, Dinner), and - one row per
sex(Male, Female).
Assign the result to g. Since each variable has two levels, you should get
a 2 × 2 grid of panels.
Check your understanding
What is "faceting" (drawing small multiples) in Seaborn?
Overlaying every group's data in a single set of axes using different colors.
Splitting the data by one or two categorical variables and drawing the same plot once per group, arranged in a grid.
Computing summary statistics for each group.
Changing the color palette across the figure.
You facet with col (3 levels) and row (4 levels). How many panels
do you get?
7, one per category level.
12 — the product of the levels (3 × 4).
1, with all groups overlaid.
3, ignoring the row variable.
In the explicit FacetGrid workflow, what does the map (or
map_dataframe) step do?
It creates the grid of empty axes.
It applies a plotting function to each data subset, drawing the same plot in every panel.
It adds the legend to the figure.
It melts the data into long form.
You facet a plot on a column that has 60 distinct values. What's the likely result, and a better approach?
A single clear chart; no problem at all.
About 60 tiny, unreadable panels — better to filter to a few categories of interest or group the rare ones.
Seaborn automatically picks the 5 most important categories.
The panels will overlap into one.
You can now break a dataset into comparable panels and know when not to. Two specialized grids remain — the all-pairs pair plot and the margin-equipped joint plot — which apply this same "set up a grid, map plots onto it" idea to specific questions.