/ Visualization - Intro to seaborn
Visualization - Intro to seaborn¶
Seaborn is a Python data-visualization library built on top of Matplotlib. It provides a high-level interface for drawing attractive statistical graphics with minimal code, and integrates tightly with pandas DataFrames.
Key advantages over raw Matplotlib:
- Sensible defaults and beautiful built-in themes
- Native DataFrame / tidy-data support
- Built-in statistical estimation (means, confidence intervals, regression lines)
- Figure-level functions (
relplot,displot,catplot) that automatically handle faceting
What you'll learn in this notebook:
- Installing and importing Seaborn
- Seaborn themes and color palettes
- Relational plots: scatter, line
- Distribution plots: histogram, KDE, ECDF, rug, violin
- Categorical plots: box, bar, strip, swarm, point
- Matrix / regression plots: heatmap, clustermap, pairplot, lmplot
Installation¶
If Seaborn is not already installed, run the cell below.
!pip install seaborn -q
Imports¶
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
print(f"Seaborn version : {sns.__version__}")
sns.set_theme(style='whitegrid', palette='muted', font_scale=1.1)
Seaborn version : 0.13.2
Built-in Example Datasets¶
Seaborn ships with several classic datasets that are loaded as pandas DataFrames.
print(sns.get_dataset_names())
['anagrams', 'anscombe', 'attention', 'brain_networks', 'car_crashes', 'diamonds', 'dots', 'dowjones', 'exercise', 'flights', 'fmri', 'geyser', 'glue', 'healthexp', 'iris', 'mpg', 'penguins', 'planets', 'seaice', 'taxis', 'tips', 'titanic']
tips = sns.load_dataset('tips')
iris = sns.load_dataset('iris')
flights = sns.load_dataset('flights')
penguins = sns.load_dataset('penguins').dropna()
fmri = sns.load_dataset('fmri')
tips.head()
| total_bill | tip | sex | smoker | day | time | size | |
|---|---|---|---|---|---|---|---|
| 0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
| 1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
| 2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
| 3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
| 4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
Themes and Color Palettes¶
Seaborn provides five built-in themes (darkgrid, whitegrid, dark, white, ticks) and a rich palette system.
styles = ['darkgrid', 'whitegrid', 'dark', 'white', 'ticks']
x = np.linspace(0, 2 * np.pi, 100)
fig, axes = plt.subplots(1, 5, figsize=(16, 3), sharey=True)
for ax, style in zip(axes, styles):
with sns.axes_style(style):
ax2 = fig.add_axes(ax.get_position(), label=style)
ax2.plot(x, np.sin(x))
ax2.set_title(style, fontsize=10)
ax2.set_xticks([])
ax.set_visible(False)
fig.suptitle('Seaborn Themes', fontsize=13, y=1.02)
plt.tight_layout()
plt.show()
/var/folders/nx/gq421vkn2h1cxq_cljnl3r1m0000gn/T/ipykernel_60397/2517397815.py:14: UserWarning: This figure includes Axes that are not compatible with tight_layout, so results might be incorrect. plt.tight_layout()
palettes = ['deep', 'muted', 'pastel', 'bright', 'dark', 'colorblind']
fig, axes = plt.subplots(len(palettes), 1, figsize=(8, 4))
for ax, pal in zip(axes, palettes):
colors = sns.color_palette(pal)
for j, color in enumerate(colors):
ax.add_patch(plt.Rectangle((j, 0), 1, 1, color=color))
ax.set_xlim(0, len(colors))
ax.set_ylim(0, 1)
ax.set_ylabel(pal, rotation=0, labelpad=55, va='center', fontsize=9)
ax.set_xticks([]); ax.set_yticks([])
fig.suptitle('Built-in Color Palettes', fontsize=12)
plt.tight_layout()
plt.show()
Relational Plots¶
Relational plots (scatterplot, lineplot, relplot) show relationships between two numerical variables.
Scatter Plot¶
scatterplot maps variables to x/y position and supports hue, size, and style semantics.
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
sns.scatterplot(data=tips, x='total_bill', y='tip',
hue='time', style='smoker', size='size',
sizes=(30, 200), alpha=0.8, ax=axes[0])
axes[0].set_title('Tips – total bill vs tip')
sns.scatterplot(data=penguins, x='flipper_length_mm', y='body_mass_g',
hue='species', style='island', alpha=0.8, ax=axes[1])
axes[1].set_title('Penguins – flipper length vs body mass')
plt.tight_layout()
plt.show()
Line Plot¶
lineplot aggregates repeated measurements and draws a confidence band by default.
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
sns.lineplot(data=fmri, x='timepoint', y='signal',
hue='event', style='event', markers=True,
dashes=False, ax=axes[0])
axes[0].set_title('fMRI signal over time (95% CI shaded)')
flights_pivot = flights.pivot(index='month', columns='year', values='passengers')
sns.lineplot(data=flights, x='year', y='passengers',
hue='month', palette='tab20', legend=False, ax=axes[1])
axes[1].set_title('Flights – passengers per year by month')
plt.tight_layout()
plt.show()
Distribution Plots¶
Distribution plots (histplot, kdeplot, ecdfplot, rugplot, displot) visualise the shape of a variable's distribution.
Histogram¶
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(data=tips, x='total_bill', hue='time',
multiple='stack', bins=25, ax=axes[0])
axes[0].set_title('Histogram – stacked by time')
sns.histplot(data=penguins, x='body_mass_g', hue='species',
multiple='dodge', shrink=0.85, bins=20, ax=axes[1])
axes[1].set_title('Histogram – dodged by species')
plt.tight_layout()
plt.show()
KDE Plot¶
Kernel Density Estimation smooths the histogram into a continuous probability density curve.
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.kdeplot(data=tips, x='total_bill', hue='time',
fill=True, alpha=0.4, linewidth=1.5, ax=axes[0])
axes[0].set_title('KDE – total bill by time')
sns.kdeplot(data=penguins, x='flipper_length_mm', y='body_mass_g',
hue='species', fill=True, alpha=0.3, ax=axes[1])
axes[1].set_title('Bivariate KDE – flipper vs body mass')
plt.tight_layout()
plt.show()
ECDF Plot¶
The Empirical CDF shows the cumulative proportion of observations below each value.
fig, ax = plt.subplots(figsize=(7, 4))
sns.ecdfplot(data=penguins, x='body_mass_g', hue='species', ax=ax)
ax.set_title('ECDF – penguin body mass by species')
ax.set_xlabel('Body mass (g)')
plt.tight_layout()
plt.show()
Rug Plot¶
Rug plots add a small tick for each individual observation along an axis, often combined with a KDE.
fig, ax = plt.subplots(figsize=(7, 4))
sns.kdeplot(data=tips, x='total_bill', fill=True, alpha=0.4, ax=ax)
sns.rugplot(data=tips, x='total_bill', height=0.06, ax=ax)
ax.set_title('KDE + Rug Plot – total bill')
plt.tight_layout()
plt.show()
Violin Plot¶
Violin plots combine a KDE with a box plot to show both the distribution shape and summary statistics.
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
sns.violinplot(data=tips, x='day', y='total_bill', hue='sex',
split=True, inner='quart', palette='pastel', ax=axes[0])
axes[0].set_title('Violin – split by sex')
sns.violinplot(data=penguins, x='species', y='flipper_length_mm',
hue='sex', inner='box', palette='muted', ax=axes[1])
axes[1].set_title('Violin – penguins flipper by sex')
plt.tight_layout()
plt.show()
Categorical Plots¶
Categorical plots (boxplot, barplot, stripplot, swarmplot, pointplot) compare distributions or aggregate statistics across discrete groups.
Box Plot¶
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
sns.boxplot(data=tips, x='day', y='total_bill', hue='smoker',
palette='Set2', ax=axes[0])
axes[0].set_title('Box Plot – tips by day and smoker')
sns.boxplot(data=penguins, x='species', y='body_mass_g',
hue='sex', palette='coolwarm', ax=axes[1])
axes[1].set_title('Box Plot – penguin mass by species & sex')
plt.tight_layout()
plt.show()
Bar Plot¶
barplot shows the mean (default) of a numeric variable per category, with a 95% confidence interval.
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
sns.barplot(data=tips, x='day', y='total_bill', hue='sex',
palette='Set1', capsize=0.1, ax=axes[0])
axes[0].set_title('Bar Plot – mean total bill by day')
sns.barplot(data=penguins, x='species', y='body_mass_g',
order=['Gentoo', 'Chinstrap', 'Adelie'],
palette='viridis', capsize=0.1, ax=axes[1])
axes[1].set_title('Bar Plot – mean body mass by species')
plt.tight_layout()
plt.show()
/var/folders/nx/gq421vkn2h1cxq_cljnl3r1m0000gn/T/ipykernel_60397/525494336.py:7: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(data=penguins, x='species', y='body_mass_g',
Strip Plot & Swarm Plot¶
Strip plots show every individual observation; swarm plots spread overlapping points to avoid overplotting.
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
sns.stripplot(data=tips, x='day', y='total_bill', hue='smoker',
dodge=True, alpha=0.6, jitter=True, ax=axes[0])
axes[0].set_title('Strip Plot – tips by day')
sns.swarmplot(data=iris, x='species', y='petal_length',
hue='species', dodge=False, size=4, ax=axes[1])
axes[1].set_title('Swarm Plot – iris petal length')
plt.tight_layout()
plt.show()
Point Plot¶
pointplot draws the mean and CI as a dot + error bar and connects groups with lines — great for showing interactions.
fig, ax = plt.subplots(figsize=(8, 5))
sns.pointplot(data=tips, x='day', y='total_bill', hue='smoker',
dodge=0.3, capsize=0.12, markers=['o', 's'],
linestyles=['-', '--'], palette='tab10', ax=ax)
ax.set_title('Point Plot – mean tip by day and smoker status')
plt.tight_layout()
plt.show()
Matrix and Regression Plots¶
Heatmap¶
heatmap renders a rectangular data array as a color-encoded matrix. Ideal for correlation matrices and pivot tables.
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Correlation matrix
corr = penguins.select_dtypes('number').corr()
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm',
vmin=-1, vmax=1, square=True, linewidths=0.5, ax=axes[0])
axes[0].set_title('Correlation Matrix – Penguins')
# Pivot table
pivot = flights.pivot(index='month', columns='year', values='passengers')
sns.heatmap(pivot, annot=True, fmt='d', cmap='YlOrRd',
linewidths=0.3, ax=axes[1])
axes[1].set_title('Flights – Passengers Pivot Table')
plt.tight_layout()
plt.show()
Clustermap¶
clustermap performs hierarchical clustering on rows and columns then draws a heatmap sorted by the resulting dendrograms.
iris_data = iris.drop('species', axis=1)
iris_norm = (iris_data - iris_data.mean()) / iris_data.std()
species_lut = dict(zip(iris['species'].unique(), ['#e74c3c','#2ecc71','#3498db']))
row_colors = iris['species'].map(species_lut)
g = sns.clustermap(iris_norm, row_colors=row_colors,
cmap='vlag', figsize=(8, 7),
row_cluster=True, col_cluster=True,
linewidths=0, yticklabels=False)
g.fig.suptitle('Clustermap – Iris (z-scored)', y=1.02)
plt.show()
Pair Plot¶
pairplot draws a grid of scatter plots for every pair of numerical variables, with univariate distributions on the diagonal.
g = sns.pairplot(penguins, hue='species', diag_kind='kde',
plot_kws=dict(alpha=0.5, edgecolors='none'),
diag_kws=dict(fill=True, alpha=0.4))
g.fig.suptitle('Pair Plot – Penguins', y=1.02)
plt.show()
Linear Regression Plot (lmplot)¶
lmplot combines a scatter plot with a fitted regression line and uncertainty band. It supports faceting via col and row.
g = sns.lmplot(data=tips, x='total_bill', y='tip',
hue='smoker', col='time',
scatter_kws=dict(alpha=0.5, s=30),
height=4, aspect=1.1)
g.set_axis_labels('Total Bill ($)', 'Tip ($)')
g.figure.suptitle('lmplot – tip vs total bill, faceted by time', y=1.04)
plt.show()
2D Plots¶
This section covers Seaborn's figure-level functions and composite 2D plot types that combine multiple views into a single figure.
Joint Plot¶
jointplot draws a bivariate scatter (or other plot) in the center and the two marginal univariate distributions on the edges — all in one figure.
# kind options: 'scatter', 'kde', 'hist', 'hex', 'reg', 'resid'
kinds = ['scatter', 'kde', 'hist', 'hex']
fig, axes_outer = plt.subplots(1, 4, figsize=(20, 5))
plt.close() # jointplot manages its own figures
for kind in kinds:
g = sns.jointplot(data=penguins, x='flipper_length_mm', y='body_mass_g',
hue='species' if kind in ('scatter','kde') else None,
kind=kind, height=4,
marginal_kws=dict(fill=True) if kind=='kde' else {})
g.figure.suptitle(f'jointplot kind={kind!r}', y=1.02)
plt.show()
relplot — Faceted Relational Plot¶
relplot is the figure-level wrapper for scatterplot and lineplot. Use col / row to create small-multiple facets.
g = sns.relplot(
data=tips, x='total_bill', y='tip',
hue='smoker', size='size', style='sex',
col='time', row='day',
height=3, aspect=1.0,
sizes=(20, 150), alpha=0.7
)
g.set_axis_labels('Total Bill ($)', 'Tip ($)')
g.figure.suptitle('relplot — Tips faceted by time × day', y=1.02)
plt.show()
displot — Faceted Distribution Plot¶
displot is the figure-level wrapper for histplot, kdeplot, and ecdfplot, with full faceting support.
fig_kws = dict(height=3.5, aspect=1.2)
g1 = sns.displot(data=penguins, x='flipper_length_mm',
hue='species', col='sex',
kind='kde', fill=True, **fig_kws)
g1.figure.suptitle('displot kind=kde — flipper length by sex', y=1.04)
g2 = sns.displot(data=penguins, x='body_mass_g', y='flipper_length_mm',
hue='species', col='species',
kind='kde', fill=True, **fig_kws)
g2.figure.suptitle('displot kind=kde bivariate — faceted by species', y=1.04)
plt.show()
catplot — Faceted Categorical Plot¶
catplot is the figure-level wrapper for all categorical plots (box, violin, bar, strip, swarm, point, count), with faceting.
g = sns.catplot(
data=tips, x='day', y='total_bill',
hue='smoker', col='time',
kind='violin', split=True,
inner='quart', palette='pastel',
height=4, aspect=1.0
)
g.set_axis_labels('Day', 'Total Bill ($)')
g.figure.suptitle('catplot kind=violin — tips by day, faceted by time', y=1.04)
plt.show()
FacetGrid¶
FacetGrid is the underlying engine behind all figure-level functions. Use it directly when you need full control over what goes into each panel.
g = sns.FacetGrid(penguins, col='species', row='sex',
height=3, aspect=1.1, margin_titles=True)
g.map_dataframe(sns.histplot, x='body_mass_g',
bins=15, kde=True, color='steelblue', alpha=0.7)
g.set_axis_labels('Body mass (g)', 'Count')
g.set_titles(col_template='{col_name}', row_template='{row_name}')
g.figure.suptitle('FacetGrid — body mass distribution by species × sex', y=1.03)
plt.show()
3D Plots¶
Seaborn does not have native 3D support, but its color palettes, themes, and style settings integrate seamlessly with Matplotlib's mpl_toolkits.mplot3d. The examples below use Seaborn palettes and the active theme to produce styled 3D figures.
from mpl_toolkits.mplot3d import Axes3D # noqa: F401
from scipy.stats import gaussian_kde
3D Scatter Plot¶
A 3D scatter plot built with Matplotlib's scatter on a 3D axes, coloured with a Seaborn palette so it respects the active theme.
species_list = penguins['species'].unique()
palette = sns.color_palette('deep', n_colors=len(species_list))
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
for sp, col in zip(species_list, palette):
sub = penguins[penguins['species'] == sp]
ax.scatter(sub['bill_length_mm'], sub['bill_depth_mm'], sub['body_mass_g'],
c=[col], label=sp, s=30, alpha=0.8, edgecolors='none')
ax.set_xlabel('Bill length (mm)')
ax.set_ylabel('Bill depth (mm)')
ax.set_zlabel('Body mass (g)')
ax.set_title('3D Scatter – Penguins (Seaborn deep palette)')
ax.legend()
plt.tight_layout()
plt.show()
3D KDE Surface¶
A bivariate KDE (computed with scipy.stats.gaussian_kde) rendered as a 3D surface using a Seaborn colormap — equivalent to Seaborn's kdeplot lifted into the third dimension.
sub = penguins[['flipper_length_mm','body_mass_g']].dropna()
xv = sub['flipper_length_mm'].values
yv = sub['body_mass_g'].values
xi = np.linspace(xv.min(), xv.max(), 60)
yi = np.linspace(yv.min(), yv.max(), 60)
Xi, Yi = np.meshgrid(xi, yi)
positions = np.vstack([Xi.ravel(), Yi.ravel()])
kernel = gaussian_kde(np.vstack([xv, yv]))
Zi = kernel(positions).reshape(Xi.shape)
fig = plt.figure(figsize=(9, 6))
ax = fig.add_subplot(111, projection='3d')
cmap = sns.color_palette('rocket', as_cmap=True)
surf = ax.plot_surface(Xi, Yi, Zi, cmap=cmap, linewidth=0, antialiased=True, alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.45, aspect=10, label='Density')
ax.set_xlabel('Flipper length (mm)')
ax.set_ylabel('Body mass (g)')
ax.set_zlabel('KDE density')
ax.set_title('3D KDE Surface – Penguins (Seaborn rocket palette)')
plt.tight_layout()
plt.show()
3D Histogram (bar3d)¶
A 3D histogram bins two variables simultaneously and draws each bin as a 3D bar. Bars are coloured by height using a Seaborn palette.
sub = penguins[['flipper_length_mm','body_mass_g']].dropna()
n_bins = 10
hist, xedges, yedges = np.histogram2d(
sub['flipper_length_mm'], sub['body_mass_g'], bins=n_bins)
xpos, ypos = np.meshgrid(
(xedges[:-1] + xedges[1:]) / 2,
(yedges[:-1] + yedges[1:]) / 2, indexing='ij')
xpos = xpos.ravel(); ypos = ypos.ravel(); zpos = np.zeros_like(xpos)
dx = (xedges[1] - xedges[0]) * 0.8
dy = (yedges[1] - yedges[0]) * 0.8
dz = hist.ravel()
norm = plt.Normalize(dz.min(), dz.max())
cmap = sns.color_palette('mako', as_cmap=True)
colors = cmap(norm(dz))
fig = plt.figure(figsize=(9, 6))
ax = fig.add_subplot(111, projection='3d')
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors, zsort='average', alpha=0.85)
sm = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
fig.colorbar(sm, ax=ax, shrink=0.45, aspect=10, label='Count')
ax.set_xlabel('Flipper length (mm)')
ax.set_ylabel('Body mass (g)')
ax.set_zlabel('Count')
ax.set_title('3D Histogram – Penguins (Seaborn mako palette)')
plt.tight_layout()
plt.show()
3D Line Plot¶
A 3D parametric curve styled with Seaborn's active colour palette — useful for trajectories, time-series in phase space, or embedding paths.
t = np.linspace(0, 4 * np.pi, 400)
curves = [
(np.cos(t), np.sin(t), t / (4*np.pi), 'Helix'),
(np.cos(t) * (1-t/(4*np.pi)), np.sin(t) * (1-t/(4*np.pi)), t/(4*np.pi), 'Cone helix'),
(np.cos(2*t), np.sin(3*t), t/(4*np.pi), 'Lissajous'),
]
palette = sns.color_palette('tab10', n_colors=len(curves))
fig = plt.figure(figsize=(12, 4))
for idx, (xs, ys, zs, label) in enumerate(curves):
ax = fig.add_subplot(1, 3, idx + 1, projection='3d')
ax.plot(xs, ys, zs, color=palette[idx], linewidth=1.5)
ax.set_title(label, fontsize=10)
ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('t')
ax.tick_params(labelsize=7)
fig.suptitle('3D Line Plots – Parametric Curves (Seaborn tab10 palette)', y=1.02)
plt.tight_layout()
plt.show()
3D Surface with Seaborn Diverging Palette¶
Seaborn's diverging palettes (vlag, icefire, coolwarm) work as Matplotlib colourmaps and are ideal for surfaces that span positive and negative values.
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2)) * np.exp(-0.15 * (X**2 + Y**2))
fig, axes_3d = plt.subplots(1, 3, figsize=(15, 5),
subplot_kw={'projection': '3d'})
div_palettes = ['vlag', 'icefire', 'RdBu_r']
for ax, pal in zip(axes_3d, div_palettes):
cmap = sns.color_palette(pal, as_cmap=True) if pal in ('vlag','icefire') else pal
surf = ax.plot_surface(X, Y, Z, cmap=cmap, linewidth=0,
antialiased=True, alpha=0.9)
fig.colorbar(surf, ax=ax, shrink=0.4, aspect=10)
ax.set_title(f'palette: {pal!r}', fontsize=9)
ax.set_xlabel('x', fontsize=8); ax.set_ylabel('y', fontsize=8)
ax.set_zlabel('z', fontsize=8)
fig.suptitle('3D Surface – Seaborn Diverging Palettes', y=1.01)
plt.tight_layout()
plt.show()
Summary¶
| Category | Function | Best for |
|---|---|---|
| Relational | scatterplot |
Two numeric variables, optional groupings |
| Relational | lineplot |
Ordered/time-series data with CI |
| Distribution | histplot |
Counts / frequency of a variable |
| Distribution | kdeplot |
Smooth density, bivariate density |
| Distribution | ecdfplot |
Cumulative distribution |
| Distribution | rugplot |
Individual observations along an axis |
| Distribution | violinplot |
Distribution shape + box-plot summary |
| Categorical | boxplot |
Quartiles + outliers across categories |
| Categorical | barplot |
Mean + CI per category |
| Categorical | stripplot / swarmplot |
All data points per category |
| Categorical | pointplot |
Mean + CI with group connections |
| Matrix | heatmap |
Matrices, correlation tables |
| Matrix | clustermap |
Clustered heatmap with dendrograms |
| Multi-plot | pairplot |
All pairwise scatter + diagonal dists |
| Regression | lmplot |
Scatter + regression line, optional facets |
Further reading:
© 2026 Ivan Cao-Berg Pittsburgh Supercomputing Center, Carnegie Mellon University
Licensed under the GNU General Public License v2.0 (GPL-2).
You may redistribute and/or modify this work under the terms of GPL-2.
This work is distributed WITHOUT ANY WARRANTY.
Happy computing.