Seaborn
Seaborn is a statistical data visualization library built on Matplotlib. It provides beautiful defaults, works directly with Pandas DataFrames, and produces complex statistical plots in one line.
Seaborn vs Matplotlib
| Basis | Matplotlib | Seaborn |
|---|
| Level | Low-level, general purpose | High-level, statistics-focused |
| Data input | Arrays/lists | DataFrames (column names directly) |
| Default aesthetics | Basic | Polished out of the box |
| Statistical plots | Manual construction | Built in (regression, distribution, categorical) |
| Grouping by a category | Manual loops | hue, col, row parameters |
| Customisation | Total control | Built on Matplotlib — full control still available |
| Code volume | High | Low |
Setup and Themes
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
sns.set_theme(style="whitegrid", palette="deep", font_scale=1.05)
# styles: darkgrid, whitegrid, dark, white, ticks
# palettes: deep, muted, pastel, bright, dark, colorblind, viridis, Set2
tips = sns.load_dataset("tips")
iris = sns.load_dataset("iris")
print(tips.head())
Categories of Seaborn Plots
1. Distribution Plots
fig, axes = plt.subplots(2, 2, figsize=(13, 8))
sns.histplot(data=tips, x="total_bill", bins=25, kde=True, ax=axes[0,0], color="#168B99")
axes[0,0].set_title("histplot — histogram + KDE")
sns.kdeplot(data=tips, x="total_bill", hue="time", fill=True, alpha=0.4, ax=axes[0,1])
axes[0,1].set_title("kdeplot — smooth density by group")
sns.ecdfplot(data=tips, x="total_bill", hue="sex", ax=axes[1,0])
axes[1,0].set_title("ecdfplot — cumulative distribution")
sns.histplot(data=tips, x="total_bill", y="tip", bins=20, cbar=True, ax=axes[1,1])
axes[1,1].set_title("histplot 2-D — bivariate density")
plt.tight_layout(); plt.show()
2. Categorical Plots
fig, axes = plt.subplots(2, 3, figsize=(17, 9))
sns.countplot(data=tips, x="day", ax=axes[0,0], palette="Set2")
axes[0,0].set_title("countplot — frequency of each category")
sns.barplot(data=tips, x="day", y="total_bill", ax=axes[0,1], palette="Set2",
errorbar=("ci", 95))
axes[0,1].set_title("barplot — mean with 95% CI")
sns.boxplot(data=tips, x="day", y="total_bill", hue="sex", ax=axes[0,2], palette="Set1")
axes[0,2].set_title("boxplot — five-number summary")
sns.violinplot(data=tips, x="day", y="total_bill", ax=axes[1,0], palette="Set3",
inner="quartile")
axes[1,0].set_title("violinplot — box plot + distribution shape")
sns.stripplot(data=tips, x="day", y="total_bill", ax=axes[1,1], alpha=0.5, jitter=0.25)
axes[1,1].set_title("stripplot — every raw point")
sns.pointplot(data=tips, x="day", y="total_bill", hue="time", ax=axes[1,2],
dodge=True, errorbar="se")
axes[1,2].set_title("pointplot — means connected, shows interaction")
plt.tight_layout(); plt.show()
barplot shows the MEAN of a numeric variable per category; countplot shows the COUNT of rows per category. Confusing the two is a common mistake.
3. Relational Plots
fig, axes = plt.subplots(1, 3, figsize=(17, 4.8))
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time",
style="smoker", size="size", sizes=(30, 250), alpha=0.7, ax=axes[0])
axes[0].set_title("scatterplot — 5 variables at once")
flights = sns.load_dataset("flights")
sns.lineplot(data=flights, x="year", y="passengers", ax=axes[1],
errorbar=("ci", 95), marker="o")
axes[1].set_title("lineplot — trend with confidence band")
sns.lineplot(data=flights, x="month", y="passengers", hue="year",
palette="viridis", legend=False, ax=axes[2])
axes[2].set_title("lineplot — seasonality by year")
axes[2].tick_params(axis="x", rotation=45)
plt.tight_layout(); plt.show()
4. Regression Plots
fig, axes = plt.subplots(1, 3, figsize=(17, 4.8))
sns.regplot(data=tips, x="total_bill", y="tip", ax=axes[0],
scatter_kws={"alpha": 0.5}, line_kws={"color": "#ef4444"})
axes[0].set_title("regplot — linear fit with 95% CI")
sns.regplot(data=tips, x="total_bill", y="tip", order=2, ax=axes[1],
scatter_kws={"alpha": 0.4}, line_kws={"color": "#10b981"})
axes[1].set_title("regplot order=2 — polynomial fit")
sns.residplot(data=tips, x="total_bill", y="tip", ax=axes[2], color="#6366f1")
axes[2].set_title("residplot — diagnostics (want a random cloud)")
plt.tight_layout(); plt.show()
# lmplot = regplot + FacetGrid (creates its own figure)
sns.lmplot(data=tips, x="total_bill", y="tip", col="time", hue="smoker",
height=4, aspect=1.1)
plt.show()
5. Matrix Plots
fig, axes = plt.subplots(1, 2, figsize=(14, 5.5))
corr = tips[["total_bill", "tip", "size"]].corr()
sns.heatmap(corr, annot=True, fmt=".3f", cmap="coolwarm", center=0,
square=True, linewidths=1, cbar_kws={"shrink": 0.8}, ax=axes[0])
axes[0].set_title("heatmap — correlation matrix")
pivot = flights.pivot(index="month", columns="year", values="passengers")
sns.heatmap(pivot, cmap="YlOrRd", ax=axes[1], cbar_kws={"label": "Passengers"})
axes[1].set_title("heatmap — time x category matrix")
plt.tight_layout(); plt.show()
# clustermap — heatmap with hierarchical clustering (Unit 3) applied to rows/columns
sns.clustermap(iris.drop(columns="species").corr(), annot=True,
cmap="coolwarm", center=0, figsize=(6.5, 6.5))
plt.show()
# Masking the upper triangle — the standard correlation heatmap presentation
numeric = iris.select_dtypes(include=np.number)
corr = numeric.corr()
mask = np.triu(np.ones_like(corr, dtype=bool))
plt.figure(figsize=(7.5, 6))
sns.heatmap(corr, mask=mask, annot=True, fmt=".2f", cmap="coolwarm",
center=0, square=True, linewidths=1, vmin=-1, vmax=1)
plt.title("Iris — Correlation (lower triangle only)", fontweight="bold")
plt.tight_layout(); plt.show()
6. Multi-Plot Grids
# pairplot — every numeric pair, coloured by a category
sns.pairplot(iris, hue="species", diag_kind="kde", height=2.2,
plot_kws={"alpha": 0.7, "s": 35}, corner=False)
plt.suptitle("Iris — Pair Plot", y=1.01, fontweight="bold")
plt.show()
# jointplot — a bivariate plot with marginal distributions
sns.jointplot(data=tips, x="total_bill", y="tip", kind="reg", height=6)
plt.show()
sns.jointplot(data=tips, x="total_bill", y="tip", kind="hex", height=6)
plt.show()
sns.jointplot(data=tips, x="total_bill", y="tip", hue="time", kind="kde", height=6)
plt.show()
# FacetGrid — the same plot repeated across subsets of the data
g = sns.FacetGrid(tips, col="time", row="smoker", height=3.4, aspect=1.25,
margin_titles=True)
g.map_dataframe(sns.scatterplot, x="total_bill", y="tip", alpha=0.7)
g.set_axis_labels("Total Bill", "Tip")
g.figure.suptitle("Tip vs Bill across Time and Smoker Status", y=1.03, fontweight="bold")
plt.show()
# The figure-level equivalents do this automatically
sns.catplot(data=tips, x="day", y="total_bill", hue="sex",
col="time", kind="box", height=4, aspect=1.1)
plt.show()
sns.displot(data=tips, x="total_bill", col="day", kde=True,
col_wrap=2, height=3.2)
plt.show()
Axes-Level vs Figure-Level Functions
| Axes-level | Figure-level |
|---|
| Examples | scatterplot, boxplot, histplot, regplot | relplot, catplot, displot, lmplot, pairplot, jointplot |
Accepts ax= | Yes — draws into an existing subplot | No — creates its own figure |
Faceting (col/row) | No | Yes |
| Use when | Building a custom multi-panel figure | You want quick faceted plots |
Palettes and Styling
# Built-in palettes
sns.color_palette("deep") # default qualitative
sns.color_palette("viridis", 8) # sequential (ordered data)
sns.color_palette("coolwarm", 8) # diverging (has a meaningful midpoint)
sns.color_palette("colorblind") # accessible — use this for public reports
# Custom palette
custom = ["#168B99", "#10b981", "#f59e0b", "#6366f1", "#ef4444"]
sns.set_palette(custom)
fig, ax = plt.subplots(figsize=(9, 5))
sns.barplot(data=tips, x="day", y="total_bill", hue="sex",
palette=["#168B99", "#f59e0b"], ax=ax, errorbar=None)
ax.set_title("Average Bill by Day and Sex", fontsize=14, fontweight="bold")
ax.set_xlabel("Day of Week"); ax.set_ylabel("Average Total Bill (Rs)")
sns.despine() # remove the top and right spines
ax.legend(title="Sex", frameon=False)
plt.tight_layout(); plt.show()
A Complete EDA Figure
sns.set_theme(style="whitegrid")
fig = plt.figure(figsize=(16, 11))
gs = fig.add_gridspec(3, 3, hspace=0.4, wspace=0.3)
ax1 = fig.add_subplot(gs[0, 0])
sns.histplot(data=tips, x="total_bill", kde=True, ax=ax1, color="#168B99")
ax1.set_title("Bill Distribution")
ax2 = fig.add_subplot(gs[0, 1])
sns.boxplot(data=tips, x="day", y="total_bill", ax=ax2, palette="Set2")
ax2.set_title("Bill by Day")
ax3 = fig.add_subplot(gs[0, 2])
sns.countplot(data=tips, x="day", hue="time", ax=ax3, palette="Set1")
ax3.set_title("Orders by Day and Time")
ax4 = fig.add_subplot(gs[1, :2])
sns.scatterplot(data=tips, x="total_bill", y="tip", hue="time", size="size",
sizes=(25, 220), alpha=0.7, ax=ax4)
sns.regplot(data=tips, x="total_bill", y="tip", scatter=False,
line_kws={"color": "#ef4444", "linewidth": 2}, ax=ax4)
ax4.set_title("Tip vs Bill with Trend Line")
ax5 = fig.add_subplot(gs[1, 2])
sns.heatmap(tips[["total_bill","tip","size"]].corr(), annot=True, fmt=".2f",
cmap="coolwarm", center=0, square=True, ax=ax5, cbar=False)
ax5.set_title("Correlations")
ax6 = fig.add_subplot(gs[2, 0])
sns.violinplot(data=tips, x="sex", y="tip", hue="smoker", split=True,
ax=ax6, palette="Set2")
ax6.set_title("Tip by Sex and Smoker")
ax7 = fig.add_subplot(gs[2, 1])
tips_pct = tips.assign(tip_pct=lambda d: d["tip"] / d["total_bill"] * 100)
sns.barplot(data=tips_pct, x="size", y="tip_pct", ax=ax7, palette="viridis",
errorbar=None)
ax7.set_title("Tip % by Party Size")
ax8 = fig.add_subplot(gs[2, 2])
sns.kdeplot(data=tips_pct, x="tip_pct", hue="time", fill=True, alpha=0.4, ax=ax8)
ax8.set_title("Tip % Distribution")
fig.suptitle("Restaurant Tips — Complete Exploratory Analysis",
fontsize=17, fontweight="bold", y=0.995)
plt.show()
Quick Reference — Which Seaborn Function
| Goal | Function |
|---|
| Distribution of one numeric variable | histplot, kdeplot, displot |
| Compare a numeric variable across groups | boxplot, violinplot, barplot |
| Count of categories | countplot |
| Relationship between two numerics | scatterplot, regplot, jointplot |
| Trend over time | lineplot |
| Correlation matrix | heatmap |
| Every pair of numeric variables | pairplot |
| Same plot across data subsets | FacetGrid, catplot, relplot, displot |
| Hierarchical structure in a matrix | clustermap |
With NumPy, Pandas, Matplotlib and Seaborn, you can analyse any dataset that fits in memory. The remaining lessons cover what happens when it doesn't.