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
- Core Syntax
- Common Operations
- Practical Examples
- Common Patterns
- Best Practices
- Common Mistakes
- Troubleshooting
- Related Cheat Sheets
- Official Documentation
Installation / Setup¶
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", orNone(bars are aggregates, not raw data). - Don't bin continuous data into categories when you can use
hueon a numeric scale orhistplot(multiple="stack"). - Check
df.corr()direction — heatmaps of correlations should usevmin=-1, vmax=1and 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") |
Related Cheat Sheets¶
- matplotlib.md — axes, annotations, and exports under seaborn
- plotly.md — interactive equivalent for exploration
- altair.md — declarative grammar-of-graphics alternative
- pandas.md — tidy data +
.corr(),.crosstabfor plots - Data Visualization category index · Home · Index
Official Documentation¶
Last Updated¶
2026-09-06 — Initial version.