Custom grids with FacetGrid and PairGrid

seaborn · Advanced · 14 min

When no figure-level function does exactly what you need, the grid classes let you facet any custom plot.

Figure-level functions are built on three grid classes you can use directly.

FacetGrid splits the data by row, col and hue and gives you one Axes per subset:

g = sns.FacetGrid(df, col="region", hue="plan", height=3)
g.map_dataframe(sns.histplot, x="spend")
g.add_legend()

g.map(func, "x", "y") calls func with the subset's columns as positional arrays, which suits plain Matplotlib functions like plt.scatter. g.map_dataframe(func, x="x", y="y") passes data=subset plus keywords, which suits seaborn functions. Your own function works too, as long as it draws on the current Axes (plt.gca()) and accepts color and label keywords.

Other useful methods: g.refline(y=0) draws a reference line in every panel, g.set(xscale="log") applies Axes properties to all, g.set_titles(col_template="{col_name}"), and iterating g.axes_dict.items() lets you annotate panels individually. Facet order comes from col_order / row_order.

PairGrid maps functions over all variable pairs with separate control of map_diag, map_lower and map_upper, such as KDE contours below the diagonal and scatter above.

JointGrid gives a central plot plus marginals: g.plot_joint(...) and g.plot_marginals(...).

Faceting creates many Axes; keep facets to a few dozen and share axes (the default) so panels stay comparable.

Example

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

rng = np.random.default_rng(7)
df = pd.DataFrame({"spend": rng.gamma(2, 20, 400),
                   "region": rng.choice(["eu", "us"], 400),
                   "plan": rng.choice(["free", "pro"], 400)})

def median_line(x, color=None, label=None, **kws):
    plt.gca().axvline(np.median(x), color=color, linestyle="--")

g = sns.FacetGrid(df, col="region", hue="plan", height=3, col_order=["us", "eu"])
g.map_dataframe(sns.histplot, x="spend", binwidth=10, alpha=0.5)
g.map(median_line, "spend")
g.add_legend()
g.set_titles(col_template="{col_name}")
print([ax.get_title() for ax in g.axes.flat])   # ['us', 'eu']
print(len(g.axes_dict["us"].lines))             # 2: one median line per plan

g.savefig("facet.png")

Remember

  • FacetGrid draws one Axes per subset and maps any function across them
  • map passes arrays positionally; map_dataframe passes data= and keywords
  • PairGrid and JointGrid give separate control of diagonal, off-diagonal and marginal plots

Watch out for

  • Writing a custom mapped function that ignores the color keyword, so hue levels all share one color.

Check yourself

  1. Which FacetGrid method should you use to map `sns.kdeplot` with `x='spend'`?

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

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

Builds on

Unlocks

Nothing builds on this yet.