Skip to content

seaborn Essentials

High-level statistical charts on top of Matplotlib. seaborn turns tidy pandas DataFrames into informative plots — distributions, group comparisons, regressions, and correlation heatmaps — with sensible defaults and automatic legends.

Table of Contents

Installation / Setup

pip install seaborn pandas     # or: uv add seaborn pandas
import seaborn as sns
import matplotlib.pyplot as plt

sns.set_theme(style="whitegrid")       # apply seaborn style to matplotlib
# styles: "whitegrid", "darkgrid", "white", "dark", "ticks"

seaborn expects tidy data: one row per observation, columns as variables. It works directly with DataFrames via data=.

Built-in datasets are handy for practice (need internet the first time):

tips = sns.load_dataset("tips")            # restaurant tips
penguins = sns.load_dataset("penguins")    # species measurements
flights = sns.load_dataset("flights")      # monthly passengers
iris = sns.load_dataset("iris")

Core Syntax

Plot families: figure-level vs axes-level

Level Functions Returns Use
Figure-level relplot, displot, catplot, lmplot, pairplot, jointplot own FacetGrid Facets, hue + col/row
Axes-level lineplot, scatterplot, histplot, kdeplot, boxplot, violinplot, barplot, countplot, heatmap a Matplotlib Axes Single panel, or pass ax=

Axes-level functions return a Matplotlib Axes — you can compose them with Matplotlib (see matplotlib.md) and place them in subplots with ax=.

Chart types quick table

Goal Function Example
Trend over x sns.lineplot(data=df, x=, y=, hue=, errorbar="sd") time series
Category totals sns.barplot(data=df, x=, y=, hue=, estimator="mean") group means
Counts per category sns.countplot(data=df, x=) frequency
Distribution (1-D) sns.histplot(data=df, x=, kde=True, bins=30) histogram + density
Smooth density sns.kdeplot(data=df, x=, hue=) PDF estimate
Two variables sns.scatterplot(data=df, x=, y=, hue=, size=) relationship
Scatter + fit sns.regplot(data=df, x=, y=) / sns.lmplot(...) correlation demo
Box per group sns.boxplot(data=df, x=, y=, hue=) median/IQR
Violin per group sns.violinplot(data=df, x=, y=) full distribution
Correlation sns.heatmap(df.corr(), annot=True, cmap="coolwarm") heatmap
Matrix of pairs sns.pairplot(df, hue=) EDA overview

Common Operations

Distributions

sns.histplot(tips, x="total_bill", bins=40, kde=True)
sns.histplot(penguins, x="body_mass_g", hue="species", multiple="stack")
sns.kdeplot(penguins, x="flipper_length_mm", hue="species", fill=True)
sns.displot(penguins, x="body_mass_g", col="species", kde=True)   # facets

Categorical comparisons

sns.boxplot(data=tips, x="day", y="total_bill")
sns.boxplot(data=tips, x="day", y="total_bill", hue="smoker")
sns.violinplot(data=tips, x="day", y="total_bill", hue="sex", split=True)
sns.barplot(data=tips, x="day", y="total_bill", errorbar="sd")    # mean ± sd
sns.catplot(data=tips, kind="box", col="time", x="day", y="total_bill")

Relationships & correlations

sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", style="smoker")
sns.lmplot(data=tips, x="total_bill", y="tip", hue="time")        # + regression line
sns.heatmap(penguins.select_dtypes("number").corr(), annot=True, fmt=".2f",
            cmap="coolwarm", vmin=-1, vmax=1)
sns.pairplot(penguins, hue="species", corner=True)

Subplots & facets

# Manual subplots: axes-level functions accept ax=
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(tips, x="total_bill", ax=axes[0])
sns.histplot(tips, x="tip", ax=axes[1])

# Automatic small multiples (figure-level)
sns.displot(penguins, x="flipper_length_mm", col="species", kind="hist")
sns.relplot(data=tips, x="total_bill", y="tip", hue="day", col="time", kind="scatter")
sns.catplot(data=tips, x="day", y="tip", kind="box", row="sex")

Legends, titles, annotations

# seaborn draws legends automatically; then fine-tune with matplotlib
ax = sns.boxplot(data=tips, x="day", y="total_bill", hue="smoker")
ax.set_title("Tips by day"); ax.set_xlabel("Day of week")
ax.legend(title="Smoker", loc="upper left")
ax.annotate("largest median", xy=(3, 25), xytext=(3.2, 30),
            arrowprops=dict(arrowstyle="->"))
ax.axhline(tips["total_bill"].median(), color="r", ls="--", lw=1)

Styles & palettes

sns.set_theme(style="whitegrid", palette="viridis")     # notebook-wide
sns.set_palette("colorblind")     # palettes: deep, muted, pastel, bright, dark, colorblind
sns.color_palette("viridis", 5)   # explicit palette as a list

# Palettes for continuous data: viridis, rocket, mako, flare
# Diverging: coolwarm, RdBu, vlag  |  Categorical: Set2, tab10, husl

Exporting

# figure-level plot -> its own figure
g = sns.displot(tips, x="total_bill", col="day")
g.figure.savefig("dist_by_day.png", dpi=300, bbox_inches="tight")

# axes-level plot -> grab the figure
ax = sns.boxplot(data=tips, x="day", y="total_bill")
ax.figure.savefig("boxplot.png", dpi=300, bbox_inches="tight")

# manual figure
fig, ax = plt.subplots(figsize=(8, 5))
sns.histplot(tips, x="total_bill", kde=True, ax=ax)
fig.savefig("hist.png", dpi=300)

Practical Examples

1. Quick EDA on a DataFrame

sns.set_theme(style="whitegrid")
df = sns.load_dataset("penguins")

# 1. Missing values first
print(df.isna().sum())

# 2. Numeric distributions by species
sns.displot(df, x="body_mass_g", hue="species", kind="kde", fill=True)

# 3. Group comparisons
sns.boxplot(data=df, x="species", y="flipper_length_mm", hue="island")

# 4. Correlation heatmap
sns.heatmap(df.select_dtypes("number").corr(), annot=True, cmap="coolwarm", fmt=".2f")

2. Two-variable relationship with facets

sns.relplot(
    data=df.dropna(), kind="scatter",
    x="bill_length_mm", y="bill_depth_mm",
    hue="species", col="island", col_wrap=2,
    height=4, aspect=1.2,
)

3. Mean comparison with error bars + count

fig, axes = plt.subplots(1, 2, figsize=(13, 4), sharey=True)
sns.barplot(data=tips, x="day", y="total_bill", errorbar="sd", ax=axes[0])
sns.countplot(data=tips, x="day", hue="time", ax=axes[1])
axes[0].set_title("mean ± sd"); axes[1].set_title("count by time")
fig.tight_layout()

Common Patterns

# Automatically cap outliers when comparing groups
sns.boxplot(data=df, x="grp", y="val", showfliers=False)

# Heatmap of a pivot table (proportions by two categories)
pivot = pd.crosstab(df["region"], df["churned"], normalize="index")
sns.heatmap(pivot, annot=True, fmt=".0%", cmap="YlGnBu")

# Joint distribution + marginals
sns.jointplot(data=df, x="x", y="y", kind="hex")     # or kind="kde"/"reg"

# Global seaborn defaults once at notebook top
sns.set_theme(style="whitegrid", rc={"figure.dpi": 110, "axes.titlesize": 12})

Best Practices

  • Feed seaborn tidy data with data= and column names as strings — no manual lists.
  • Let figure-level functions make the facets; use ax= only for custom grids.
  • Choose the right error bar: errorbar="sd", "ci", or None (bars are aggregates, not raw data).
  • Don't bin continuous data into categories when you can use hue on a numeric scale or histplot(multiple="stack").
  • Check df.corr() direction — heatmaps of correlations should use vmin=-1, vmax=1 and a diverging map.
  • Export the right object: figure-level → .figure.savefig; axes-level → ax.figure.savefig.
  • seaborn draws on Matplotlib — combine it freely with Matplotlib's legends, annotations, and rcParams.

Common Mistakes

Mistake Consequence Correct approach
sns.histplot(df["col"]) without data= Works but loses hue/other columns sns.histplot(data=df, x="col")
Expecting raw values in barplot Bars show the mean, not raw data Use countplot or plot the raw values
Old ci= parameter Error on recent seaborn Use errorbar="sd" / "ci" / None
Not passing ax= in manual subplots All panels stack on the last axes Pass ax=axes[i] to every axes-level call
load_dataset fails offline Needs internet once Download CSVs once; pd.read_csv after
Ignoring categorical order Alphabetical order you didn't intend order=[...] / hue_order=[...]
Plotting wide data (one col per series) Wrong mapping pd.melt to tidy first

Troubleshooting

Symptom Cause Fix
ValueError: Could not interpret input 'col' Column name typo or not in df Check df.columns
Nothing rendered in Jupyter seaborn needs matplotlib inline %matplotlib inline
Colorbar appears for a numeric hue Numeric hue becomes continuous hue=df["col"].astype(str) for categorical
kde fails on skewed data Bandwidth issues on heavy tails cut=0, bw_adjust=0.5, or log-transform
Legend overlaps the plot Auto placement ax.legend(bbox_to_anchor=(1.02, 1))
Ugly fonts after set_theme Style applies to whole session sns.set_theme() inside a with? not supported — reset with plt.style.use("default")

Official Documentation

Last Updated

2026-09-06 — Initial version.