Heatmap
ggplot2: geom_tile() · Package: ggplot2 · Variables: 2 categorical + 1 numerical
WHAT IS A HEATMAP?
A heatmap displays values in a matrix grid where each cell is colored according to its value. It reveals patterns across two dimensions simultaneously — like which day of the week and time of day sees the most website traffic, or which variables in a dataset are most correlated. Heatmaps are particularly powerful for correlation matrices, user activity patterns, and any data that can be arranged in a rows-by-columns format. Use a diverging color scale (e.g., blue-white-red) for data with a meaningful center point. In ggplot2, use geom_tile() with scale_fill_gradient2().
HOW TO READ A HEATMAP
A heatmap encodes magnitude as colour across a two-dimensional grid, so patterns emerge as bands, blocks and clusters rather than as individual readable values. Accept that trade-off deliberately: colour is poor for precise comparison and excellent for spotting structure, which makes a heatmap the wrong choice when the reader needs exact numbers and the right one when they need to see where something concentrates. The palette must match the data. Sequential scales, running light to dark, suit quantities with a natural zero; diverging scales, with a neutral midpoint, suit data where deviation in either direction from a centre is the point — correlations, differences from a target, year-on-year change. Ordering is not cosmetic either: rows and columns arranged alphabetically usually show nothing, while the same matrix seriated by similarity or clustered often reveals block structure immediately.
BEST FOR
- · Correlation matrices
- · Two-dimensional patterns
- · Time-of-day/day-of-week patterns
- · Large numeric tables
AVOID WHEN
- · Precise values needed
- · Few data points
- · Color-blind accessibility concerns
R + GGPLOT2 CODE EXAMPLE
cor_mat <- reshape2::melt(cor(mtcars[, 1:7])) ggplot(cor_mat, aes(x = Var1, y = Var2, fill = value)) + geom_tile() + scale_fill_gradient2(low = "blue", mid = "white", high = "#ff6a00") + labs(title = "Correlation Matrix")
CORRELATION HEATMAP, CLUSTERED WITH VALUE LABELS
A correlation matrix with a diverging palette centred on zero, rows and columns ordered by hierarchical clustering, and the coefficients printed in each cell.
library(ggplot2)
library(dplyr)
library(tidyr)
cm <- cor(mtcars[, 1:7])
# order by clustering so related variables sit together
ord <- hclust(as.dist(1 - cm))$order
cm <- cm[ord, ord]
cor_long <- as.data.frame(cm) |>
tibble::rownames_to_column("var1") |>
pivot_longer(-var1, names_to = "var2", values_to = "r") |>
mutate(across(c(var1, var2), ~ factor(.x, levels = rownames(cm))))
ggplot(cor_long, aes(x = var1, y = var2, fill = r)) +
geom_tile(colour = "white", linewidth = 0.5) +
geom_text(aes(label = sprintf("%.2f", r)), size = 3) +
scale_fill_gradient2(
low = "#3b6ea5", mid = "white", high = "#ff6a00",
midpoint = 0, limits = c(-1, 1), name = "r"
) +
coord_fixed() +
labs(title = "Correlation matrix, clustered", x = NULL, y = NULL) +
theme_minimal(base_size = 12) +
theme(panel.grid = element_blank(),
axis.text.x = element_text(angle = 45, hjust = 1))COMMON MISTAKES WITH THIS CHART
Using a rainbow palette
Rainbow scales are not perceptually uniform — they create false boundaries where hue shifts fast and hide real differences where it shifts slowly. Use viridis or a ColorBrewer sequential scale.
Applying a sequential palette to diverging data
For correlations or differences from a baseline, use scale_fill_gradient2() with a midpoint at zero so positive and negative read as genuinely opposite.
Leaving rows and columns in their incoming order
Reorder by cluster or by a summary statistic. Block structure that is invisible alphabetically frequently becomes obvious once similar rows sit together.
Relying on colour alone for critical values
Around 8% of men have some form of colour vision deficiency. Add geom_text() labels for the cells that matter, and check the palette with colorspace::deutan().
Run this code now
Paste the code above into RChat and see the heatmap rendered instantly in your browser.