seaborn
Seaborn is a statistical visualization library that wraps matplotlib to map tidy pandas DataFrames directly to plot types—scatter, histogram, violin, regression, heatmap—with automatic aggregation, confidence intervals, and perceptually uniform color palettes, so that the gap between "I have a DataFrame" and "I have a publication-quality statistical figure" shrinks from dozens of matplotlib calls to one function call.
```svg
```
**Seaborn's core abstraction is the mapping from a tidy DataFrame column name to a visual channel—x-position, y-position, hue, size, style—so that the same function call handles both the split-apply-combine aggregation across groups and the layout of the resulting artists on a shared axis.** Calling `sns.lineplot(data=df, x='step', y='loss', hue='model')` groups `df` by the `model` column, computes the mean and 95% confidence interval (bootstrapped from 1,000 resamples by default) within each group, and draws a separate line with a shaded CI band per group—operations that in raw matplotlib require a manual `groupby`, bootstrap loop, `ax.fill_between`, and color cycle management. The hue semantic handles both categorical and continuous data, switching from a qualitative palette to a sequential colormap depending on the column's dtype.
**Kernel density estimation underlies violinplot, kdeplot, and the diagonal of pairplot, with bandwidth selected by Scott's rule: h = 1.06σN^(−1/5), which narrows from 0.266 at N = 1,000 to 0.168 at N = 10,000 as more data resolves finer distributional structure.** The KDE computation in SciPy's `gaussian_kde` uses an FFT-based convolution for large samples, reducing the naive O(N²) per-point evaluation to O(N log N): for 100,000 points the FFT path completes in ~20 ms versus ~8 s for the naive double-loop—a 400× speedup. The bandwidth choice controls the bias-variance tradeoff—a small h reveals multimodality but adds noise bumps; a large h smooths over real structure. `sns.kdeplot(bw_adjust=0.5)` halves Scott's default, and `bw_adjust=2` doubles it.
**FacetGrid is seaborn's mechanism for conditioning a plot on one or two categorical variables, creating a grid of independent matplotlib Axes where each cell applies the same plot function to the corresponding data subset.** A `FacetGrid(df, row='diet', col='exercise')` with 5 diet categories and 4 exercise levels produces 20 Axes objects on a single Figure, each scoped to one combination; `grid.map(sns.histplot, 'weight')` then applies the histogram to each subset independently. This is equivalent to 20 manual `plt.subplot()` calls followed by 20 filtered `histplot()` calls, but FacetGrid additionally aligns axis limits across rows and columns, shares axis labels at the margins, and handles legend placement—approximately 50 lines of matplotlib code replaced by 3. Render time for a 5×4 FacetGrid with 1,000-row subsets is typically 2–4 s depending on the plot type.
**The pairplot function builds a 5×5 grid of 25 subplots for a 5-column DataFrame, placing KDE estimates on the diagonal and scatter plots on off-diagonal cells, and is the fastest way to survey all pairwise relationships in a dataset but becomes slow above 10 columns because KDE cost grows with the number of cells.** Each off-diagonal scatter calls `ax.scatter()` directly (no additional aggregation), while each diagonal KDE runs the FFT convolution independently; for a 1,000-row, 5-column DataFrame, total render time is approximately 2–3 s. At 10 columns the 100-subplot grid takes 15–20 s; switching to `diag_kind='hist'` cuts diagonal render cost by ~70%. At 10 columns the grid has 100 subplots and render time reaches 15–20 seconds; switching to a sample of 500 rows or disabling KDE with `diag_kind='hist'` recovers interactive speed.
**Seaborn's color palette system distinguishes three palette classes—qualitative (categorical hue), sequential (ordered numeric), and diverging (signed deviation from a midpoint)—and defaults to ColorBrewer-inspired schemes with accessibility for the most common forms of color-vision deficiency.** The default `deep` palette provides 10 perceptually uniform colors in HUSL space (lightness fixed at L=65), where perceived brightness is held constant across hues so that no single color draws more attention than another in a multi-line plot. Palettes cycle beyond 10 categories with ~15% perceptual distance reduction per repeat. Calling `sns.color_palette('colorblind')` selects a palette validated against deuteranopia and protanopia simulations; `sns.color_palette('viridis', n_colors=8)` returns 8 samples from matplotlib's viridis colormap for ordered data where magnitude matters.
**Every seaborn function returns the underlying matplotlib Axes object, making it composable with the full matplotlib API without any wrapper or escape hatch.** After `ax = sns.boxplot(data=df, x='group', y='value')`, calling `ax.set_title('My Title')`, `ax.set_xlim(0, 10)`, or `ax.axhline(y=0, color='red')` applies exactly as it would to any manually constructed matplotlib Axes. This design makes seaborn compatible with multi-panel layouts produced by `plt.subplots()`: `fig, axes = plt.subplots(1, 2); sns.scatterplot(ax=axes[0], ...); sns.histplot(ax=axes[1], ...)` works without any seaborn-specific layout machinery. The `ax=` parameter is the bridge between seaborn's statistical abstraction and matplotlib's positioning control.
| Plot type | Statistical operation | SciPy / statsmodels call | ~Time (1k rows) |
|---|---|---|---|
| `kdeplot` | KDE with Scott bandwidth | `gaussian_kde` FFT | 20 ms |
| `regplot` | OLS + 95% CI bootstrap | `np.polyfit` + 1000 resamples | 80 ms |
| `violinplot` | KDE per group | `gaussian_kde` × N groups | 30 ms |
| `pairplot` | KDE + scatter grid | 25 Axes render | 2–3 s |
| `clustermap` | Hierarchical clustering | `scipy.cluster.hierarchy` | 200 ms |
```
SEABORN CALL FLOWCHART
sns.lineplot(data=df, x='step', y='loss', hue='model')
│
▼
┌─────────────────────┐
│ Tidy data check │ expects long-form DataFrame
│ column name lookup │ maps 'model' → hue channel
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Split-apply-combine│ df.groupby('model')[['step','loss']]
│ per hue group │ mean + 95% CI (1000 bootstrap resamples)
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ Color assignment │ palette → one color per hue level
│ (HUSL / deep) │ 10 colors before cycling
└────────┬────────────┘
│
▼
┌─────────────────────┐
│ matplotlib draw │ ax.plot() + ax.fill_between() per group
│ returns Axes │ ax.set_xlabel/ylabel auto-set to col names
└─────────────────────┘
```
Read seaborn through a *statistical grammar* lens rather than a *prettier matplotlib* lens. The library's job is not to make matplotlib easier to style—rcParams and `plt.style.use` do that—but to encode the contract between a tidy data column and a visual channel (position, hue, size, style), and to insert the correct statistical transformation (KDE, OLS, bootstrap CI, hierarchical clustering) automatically between the raw data and the matplotlib artist. Every seaborn function is a pipeline: data → groupby → statistical summary → color mapping → matplotlib call → return Axes. Understanding that pipeline is what makes the difference between knowing which seaborn function to call and knowing how to fix it when the output is wrong.