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
mappasses arrays positionally;map_dataframepassesdata=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
colorkeyword, so hue levels all share one color.