Seaborn Heatmap Tutorial: sns.heatmap() Explained with Examples

⏱️ 4 min read 📈 Visualization

sns.heatmap() turns any 2-D array or DataFrame into a color-encoded grid in one line — and about five of its parameters (annot, fmt, cmap, center, mask) cover 95% of real use. This tutorial walks through each, ending with the masked correlation matrix that's the most common heatmap in data science.

Quick answer: Call sns.heatmap(data) where data is a DataFrame or 2-D array — each cell is colored by its value. Add annot=True, fmt=".1f" to print values in the cells, cmap="coolwarm", center=0 for correlation matrices, and mask=np.triu(np.ones_like(corr, dtype=bool)) to hide the redundant upper triangle.

How Do You Make a Basic Heatmap in Seaborn?

Give sns.heatmap() anything rectangular: a DataFrame, a NumPy array, or — most usefully — the output of pivot_table(). Row and column labels come straight from the DataFrame's index and columns, so shaping the data well is 90% of the work.

import seaborn as sns
import matplotlib.pyplot as plt

flights = sns.load_dataset("flights")
pivot = flights.pivot_table(index="month", columns="year",
                            values="passengers")

sns.heatmap(pivot)
plt.show()

If your data is a long/tidy table of records rather than a grid, pivot it first — that pivot_table step is the seaborn equivalent of arranging cells in a spreadsheet, the same shaping step as an Excel heatmap. Building the grid with pandas operations instead of Python loops keeps it fast on large data; see vectorization in Python for why.

What Do annot and fmt Do in sns.heatmap()?

annot=True prints each cell's value on top of its color, and fmt controls the number format — "d" for integers, ".2f" for two decimals, ".0%" for percentages. The default fmt is scientific notation (".2g"), which looks broken for ordinary numbers, so always set fmt when you set annot.

sns.heatmap(pivot,
            annot=True,          # write the value in each cell
            fmt="d",             # integer format ("," needs fmt=",d" via
                                 # annot_kws or preformatted strings)
            linewidths=0.5,      # thin gaps between cells
            cbar=False)          # numbers shown, so drop the colorbar

Two practical notes: annotations only work up to roughly a 20 × 20 grid before the text overlaps, and if you pass annot a string DataFrame with fmt="" you can display custom labels (like "12 (+3%)") while coloring by a separate numeric grid.

How Do You Plot a Correlation Matrix with the Upper Triangle Masked?

A correlation matrix is symmetric — the upper triangle repeats the lower — so the standard move is to mask it. Pass a boolean array to mask: cells where the mask is True are left blank. np.triu builds exactly that upper-triangle mask.

import numpy as np

corr = df.select_dtypes("number").corr()

mask = np.triu(np.ones_like(corr, dtype=bool))   # True above diagonal

plt.figure(figsize=(9, 7))
sns.heatmap(corr,
            mask=mask,
            annot=True, fmt=".2f",
            cmap="coolwarm",
            center=0,             # anchor white at correlation 0
            vmin=-1, vmax=1,      # full theoretical range
            square=True,
            linewidths=0.5)
plt.title("Feature correlations")
plt.tight_layout()
plt.show()

Setting vmin=-1, vmax=1 matters: without it, seaborn scales colors to your data's observed range, so a matrix whose strongest correlation is 0.4 looks as dramatic as one full of 0.9s. Fixing the limits keeps color meaning comparable across plots.

Which Colormap Should You Use for a Heatmap?

Match the colormap to the data's structure. Use a sequential map ("viridis", "YlOrRd", "Blues") for magnitudes that run from low to high, and a diverging map ("coolwarm", "RdBu_r", "vlag") with center=0 for values that deviate from a midpoint — correlations, deltas, z-scores. Never use a diverging map for plain counts: the midpoint color implies a meaningful zero that isn't there.

# Magnitudes (counts, sales, temperatures):
sns.heatmap(pivot, cmap="viridis")

# Deviations (correlations, YoY change, residuals):
sns.heatmap(corr, cmap="coolwarm", center=0)

# Reverse any map with the _r suffix:
sns.heatmap(pivot, cmap="Blues_r")

Avoid the old rainbow maps ("jet"): they create false boundaries and fail for colorblind readers. viridis and coolwarm are both perceptually safe defaults — the deeper reasoning is in heat maps explained.

How Do You Control Figure Size and Label Readability?

Seaborn draws on the current matplotlib figure, so set the size before calling sns.heatmap() with plt.figure(figsize=(w, h)). A workable rule: about 0.4–0.5 inches per row and column, and rotate crowded x-labels.

n_rows, n_cols = pivot.shape
plt.figure(figsize=(max(6, 0.5 * n_cols), max(4, 0.4 * n_rows)))

ax = sns.heatmap(pivot, cmap="YlGnBu", square=False)
ax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha="right")
plt.tight_layout()          # stop labels being clipped
plt.savefig("heatmap.png", dpi=150, bbox_inches="tight")

square=True forces square cells (right for correlation matrices, wasteful for wide time grids), and tight_layout() or bbox_inches="tight" fixes the chopped-off-labels problem that hits nearly everyone's first saved heatmap. For more on seaborn's strengths beyond heatmaps, see the seaborn tool review.

Pro Tip: For a correlation heatmap people can actually read, cluster it: sns.clustermap(corr, cmap="coolwarm", center=0) reorders rows and columns so correlated variables sit next to each other, turning a noise-speckled matrix into visible blocks of related features — one function call, no extra work.

← Back to Visualization Tips