ggcorrplot: Correlation Matrix Heatmap in R with ggplot2

Turn a correlation matrix into a publication-ready ggplot2 heatmap — squares or circles, triangles, reordering, coefficients and significance marks — with ggcorrplot, plus the geom_tile() recipe underneath

Biostatistics

Visualize a correlation matrix as a ggplot2 heatmap in R. Compute it with cor(), then draw it in one call with ggcorrplot() — squares or circles, upper/lower triangles, hierarchical reordering, coefficients on the tiles, and non-significant cells crossed out — and see the underlying geom_tile() + scale_fill_gradient2() recipe the package builds for you.

Published

July 10, 2026

Modified

July 11, 2026

TipKey takeaways
  • A correlation heatmap is a correlation matrix drawn as a coloured grid — the fastest way to see which variables move together.
  • Compute the matrix with base cor(), then draw it in one call with ggcorrplot::ggcorrplot() — the result is an ordinary ggplot2 object you can restyle and extend.
  • Choose a method ("square" or "circle"), a layout (type = "lower"/"upper"), and reorder with hc.order = TRUE to surface clusters of related variables.
  • Print the coefficients with lab = TRUE, and pass a p-value matrix (cor_pmat()) so non-significant cells are crossed out (insig = "pch") or hidden (insig = "blank").
  • Under the hood it is just geom_tile() + scale_fill_gradient2() — this lesson also builds that heatmap by hand so you can customise every layer.

Introduction

You have a table of numbers and want to know how they relate — does weight rise as mileage falls? which variables carry the same signal? A correlation heatmap answers this at a glance: it encodes every pairwise correlation in a coloured grid, so strong positive and negative relationships pop out and redundant variables cluster together.

This lesson uses ggcorrplot — a small package that draws a correlation matrix as a ggplot2 plot — on the built-in mtcars data. You will compute the matrix once with base cor(), then draw it in a single call, adding reordering, coefficients and significance marks. Then, because searchers often want the raw recipe, you will build the same heatmap by hand with geom_tile() and scale_fill_gradient2() — which is exactly what ggcorrplot() packages for you.

Prefer round glyphs and a base-graphics workflow? The correlogram lesson does the same job with the corrplot package. Reach for ggcorrplot when you want a ggplot2 object you can theme and compose with +.

Compute the correlation matrix

Every heatmap starts from a correlation matrix. cor() returns the pairwise Pearson correlations; rounding to one decimal keeps the coefficients readable when you print them on the tiles later.

library(ggcorrplot)

data(mtcars)
corr <- round(cor(mtcars), 1)
corr[1:6, 1:6]   # a corner of the 11x11 matrix
      mpg  cyl disp   hp drat   wt
mpg   1.0 -0.9 -0.8 -0.8  0.7 -0.9
cyl  -0.9  1.0  0.9  0.8 -0.7  0.8
disp -0.8  0.9  1.0  0.8 -0.7  0.9
hp   -0.8  0.8  0.8  1.0 -0.4  0.7
drat  0.7 -0.7 -0.7 -0.4  1.0 -0.7
wt   -0.9  0.8  0.9  0.7 -0.7  1.0

To mark significance later, you also need the matrix of correlation p-values. cor_pmat() [in ggcorrplot] computes it by running stats::cor.test() on every pair:

library(ggcorrplot)

p.mat <- cor_pmat(mtcars)
signif(p.mat[1:6, 1:6], 2)
         mpg     cyl    disp      hp    drat      wt
mpg  0.0e+00 6.1e-10 9.4e-10 1.8e-07 1.8e-05 1.3e-10
cyl  6.1e-10 0.0e+00 1.8e-12 3.5e-09 8.2e-06 1.2e-07
disp 9.4e-10 1.8e-12 0.0e+00 7.1e-08 5.3e-06 1.2e-11
hp   1.8e-07 3.5e-09 7.1e-08 0.0e+00 1.0e-02 4.1e-05
drat 1.8e-05 8.2e-06 5.3e-06 1.0e-02 0.0e+00 4.8e-06
wt   1.3e-10 1.2e-07 1.2e-11 4.1e-05 4.8e-06 0.0e+00

ggcorrplot() never computes correlations itself — it takes a matrix you already have, so it works equally well with a Spearman matrix, a partial-correlation matrix, or any square matrix of coefficients.

Draw it in one call with ggcorrplot()

Pass the matrix and you get the full heatmap. The default encodes each correlation as a coloured square: red for positive, blue for negative, with the intensity scaled to the coefficient. (The default palette is colors = c("blue", "white", "red"), read low → mid → high, so +1 is red.)

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
ggcorrplot(corr)

A ggcorrplot heatmap of the mtcars correlation matrix as coloured squares, red for positive and blue for negative correlations.

Squares or circles

method = "circle" encodes the correlation with the circle’s area instead of a filled square — useful when you want the magnitude, not just the sign, to jump out.

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
ggcorrplot(corr, method = "circle")

The same mtcars correlation matrix drawn with ggcorrplot's circle method: each correlation is a circle whose area and colour encode its strength and sign.

Reorder by hierarchical clustering

In variable order, the structure is hard to see. Set hc.order = TRUE to reorder the variables by hierarchical clustering so that correlated variables sit next to each other — blocks of related variables become obvious. outline.color draws a clean separator around each tile.

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
ggcorrplot(corr, hc.order = TRUE, outline.color = "white")

The mtcars correlation heatmap reordered by hierarchical clustering, so mutually correlated variables such as cyl, disp, hp and wt cluster together in a visible block.

Show one triangle

A correlation matrix is symmetric, so the two triangles carry the same information. Drop the redundant half with type = "lower" (or type = "upper"):

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
ggcorrplot(corr, hc.order = TRUE, type = "lower", outline.color = "white")

A lower-triangle ggcorrplot heatmap of the reordered mtcars matrix, showing each pairwise correlation once.

Mark statistical significance

A correlation can be close to zero by chance. Supply the p-value matrix as p.mat and ggcorrplot() flags which correlations are significant at sig.level (default 0.05). Three styles, chosen with insig:

insig = "pch" (the default) crosses out the non-significant cells:

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
p.mat <- cor_pmat(mtcars)
ggcorrplot(corr, hc.order = TRUE, type = "lower", p.mat = p.mat)

The lower-triangle mtcars correlation heatmap with non-significant correlations crossed out by an X, leaving only the statistically reliable relationships.

insig = "blank" hides them entirely, so only the significant correlations remain:

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
p.mat <- cor_pmat(mtcars)
ggcorrplot(corr, hc.order = TRUE, type = "lower", p.mat = p.mat, insig = "blank")

The lower-triangle mtcars correlation heatmap with non-significant correlations left blank, showing only the significant cells.

insig = "stars" flips the emphasis: instead of crossing out the non-significant cells, it appends significance stars (***, **, * for p < 0.001, 0.01, 0.05) to the significant ones. With lab = TRUE you get the coefficient and its significance in one figure — the publication-ready view:

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
p.mat <- cor_pmat(mtcars)
ggcorrplot(corr, hc.order = TRUE, type = "lower",
           lab = TRUE, insig = "stars", p.mat = p.mat)

A lower-triangle ggcorrplot heatmap of the reordered mtcars matrix with the correlation coefficient and significance stars printed in each square.

Custom colours and theme

colors sets the diverging gradient as a length-3 vector (low / mid / high), and ggtheme swaps the base ggplot2 theme. tl.col and tl.srt style the variable-name labels.

library(ggcorrplot)

corr <- round(cor(mtcars), 1)
ggcorrplot(corr, hc.order = TRUE, type = "lower", outline.color = "white",
           ggtheme = ggplot2::theme_minimal,
           colors = c("#6D9EC1", "white", "#E46726"),
           tl.col = "gray30", tl.srt = 45)

The lower-triangle mtcars correlation heatmap restyled with a blue-white-orange diverging palette, a minimal theme, and grey rotated axis labels.

Because ggcorrplot() returns a plain ggplot object, you can also refine it with the usual grammar — add a labs() title, a caption, or any extra layer with +.

Build the same heatmap by hand with geom_tile()

ggcorrplot() is a convenient wrapper around a standard ggplot2 idiom: reshape the matrix to long form, map the correlation to fill, and draw one tile per cell. Building it yourself is worth knowing — you get full control over every layer, and it works for any matrix, not just correlations.

Reshape the matrix to long form

ggplot2 wants one row per cell, not a square matrix. Base R does the reshape without any extra package: as.table() turns the matrix into a contingency-style table and as.data.frame() unrolls it into three columns — Var1, Var2 and Freq (the correlation value):

cormat <- round(cor(mtcars[, c("mpg", "disp", "hp", "drat", "wt", "qsec")]), 2)

long <- as.data.frame(as.table(cormat))
head(long)
  Var1 Var2  Freq
1  mpg  mpg  1.00
2 disp  mpg -0.85
3   hp  mpg -0.78
4 drat  mpg  0.68
5   wt  mpg -0.87
6 qsec  mpg  0.42
Note

Older tutorials reach for reshape2::melt() here. Base R’s as.data.frame(as.table(m)) does the same job with no dependency — handy inside the in-browser sandbox.

Map correlation to fill

Now draw one tile per cell and colour it by the coefficient. scale_fill_gradient2() is the key: it is a diverging scale anchored at midpoint = 0, so zero correlations are white, positives lean one way and negatives the other. limit = c(-1, 1) fixes the scale to the full correlation range, and coord_fixed() keeps the tiles square.

library(ggplot2)

cormat <- round(cor(mtcars[, c("mpg", "disp", "hp", "drat", "wt", "qsec")]), 2)
long <- as.data.frame(as.table(cormat))

ggplot(long, aes(Var1, Var2, fill = Freq)) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = "#3a86d4", high = "#b2182b", mid = "white",
                       midpoint = 0, limit = c(-1, 1), name = "Correlation") +
  coord_fixed() +
  theme_minimal()

A hand-built ggplot2 correlation heatmap of six mtcars variables using geom_tile and a blue-white-red diverging fill scale.

Mask the redundant triangle

To show only the upper triangle, set the lower half of the matrix to NA before reshaping, then drop the empty cells. upper.tri() / lower.tri() give the logical masks:

library(ggplot2)

cormat <- round(cor(mtcars[, c("mpg", "disp", "hp", "drat", "wt", "qsec")]), 2)
cormat[lower.tri(cormat)] <- NA          # keep the upper triangle

long <- as.data.frame(as.table(cormat))
long <- long[!is.na(long$Freq), ]        # drop the masked cells

ggplot(long, aes(Var1, Var2, fill = Freq)) +
  geom_tile(color = "white") +
  scale_fill_gradient2(low = "#3a86d4", high = "#b2182b", mid = "white",
                       midpoint = 0, limit = c(-1, 1), name = "Correlation") +
  coord_fixed() +
  theme_minimal()

The hand-built mtcars correlation heatmap showing only the upper triangle, with the redundant lower half masked out.

Reorder by clustering and add the coefficients

Finally, reproduce the two ggcorrplot touches by hand: reorder the variables by hierarchical clustering (cluster on the correlation-based distance (1 - r) / 2), then print the coefficients with geom_text().

library(ggplot2)

cormat <- round(cor(mtcars[, c("mpg", "disp", "hp", "drat", "wt", "qsec")]), 2)

# Reorder by hierarchical clustering on a correlation distance
dd <- as.dist((1 - cormat) / 2)
ord <- hclust(dd)$order
cormat <- cormat[ord, ord]

cormat[lower.tri(cormat)] <- NA
long <- as.data.frame(as.table(cormat))
long <- long[!is.na(long$Freq), ]

ggplot(long, aes(Var1, Var2, fill = Freq)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Freq), size = 3) +
  scale_fill_gradient2(low = "#3a86d4", high = "#b2182b", mid = "white",
                       midpoint = 0, limit = c(-1, 1), name = "Correlation") +
  coord_fixed() +
  theme_minimal() +
  theme(axis.title = element_blank())

A finished hand-built ggplot2 correlation heatmap of six mtcars variables, reordered by hierarchical clustering, upper triangle only, with the coefficients printed in each tile.

That is every feature ggcorrplot() gave you in one call — reordering, a masked triangle, coefficients on the tiles — reproduced from the raw grammar. Use the package for speed; drop to geom_tile() when you need a layer it does not expose.

Try it live

Draw a correlation heatmap of a different dataset, or change the method, layout and colours. The sandbox boots on the first Run.

🟢 With an AI agent

Ask Prova “draw a correlation heatmap of my data with ggcorrplot, reorder it to show clusters, print the coefficients, and cross out the non-significant correlations” — it answers with code you can run on your own data. The runtime is the judge. Ask Prova →

Common issues

Non-numeric columns break cor(). cor() needs an all-numeric matrix — drop or convert factor and character columns first, e.g. cor(mydata[sapply(mydata, is.numeric)]).

Missing values give NA correlations. With incomplete data, add use = "pairwise.complete.obs" to cor() so each correlation uses the observations that are present.

ggcorrplot() doesn’t compute correlations. It expects a correlation matrix, not raw data. Passing a data frame errors — compute cor() (and cor_pmat()) first, then pass the matrix.

Frequently asked questions

Compute the matrix with cor(data), then draw it in one call with ggcorrplot::ggcorrplot(corr). Add hc.order = TRUE to cluster related variables, type = "lower" to drop the redundant half, lab = TRUE to print the coefficients, and a p-value matrix via p.mat to mark significance.

Both visualize a correlation matrix. ggcorrplot returns a ggplot2 object, so you can restyle it with themes and add layers with +; corrplot is a base-graphics package with more glyph options (pie, ellipse, mixed layouts). If you already work in ggplot2, prefer ggcorrplot; for a base-graphics correlogram, use corrplot.

With ggcorrplot, set lab = TRUE. Building the heatmap by hand, add a geom_text(aes(label = Freq)) layer after geom_tile(). Rounding the matrix first (round(cor(x), 1)) keeps the printed values readable.

Compute a p-value matrix with cor_pmat(data), then pass it as p.mat. insig = "pch" (the default) crosses out the non-significant cells; insig = "blank" hides them; insig = "stars" instead appends significance stars to the significant ones.

Correlations range from −1 to 1 and are meaningful in both directions, so a diverging scale centred at 0 maps zero to a neutral colour (white) and pushes positive and negative correlations to opposite hues. limit = c(-1, 1) fixes the scale to the full range so colours are comparable across plots.

Test your understanding

  1. Run it. In the live cell below, draw an upper-triangle, hc.ordered ggcorrplot of the four numeric iris columns, with the coefficients printed. Fill the blank.
  2. Conceptual. You pass p.mat and see two cells crossed out. What does a crossed-out cell mean, and why should you not interpret it?

Fill the blank with "upper" (or "lower"). For question 2, recall that p.mat carries the p-values and ggcorrplot() crosses out the cells whose p-value exceeds sig.level.

corr <- round(cor(iris[, 1:4]), 1)
ggcorrplot(corr, type = "upper", hc.order = TRUE, lab = TRUE)
#> Petal.Length and Petal.Width cluster together (r near 1).

For question 2: a crossed-out cell is a correlation that is not statistically significant at the chosen sig.level (default 0.05) — its p-value is above the threshold, so the relationship could plausibly be zero. Interpret only the cells that survive.

Quick check. Which argument reorders the variables so correlated ones sit together?

hc.order = TRUE — it reorders the matrix by hierarchical clustering. type controls which triangle shows, lab prints the coefficients, and p.mat marks significance.

Conclusion

You can now build a correlation matrix heatmap in R two ways: the fast path with ggcorrplot::ggcorrplot() — one call for squares or circles, reordering, one triangle, coefficients and significance marks — and the manual path with geom_tile() + scale_fill_gradient2(), which is exactly what the package wraps. Reach for ggcorrplot day to day; drop to raw ggplot2 when you need a layer it does not expose.

Reuse

Citation

BibTeX citation:
@online{2026,
  author = {},
  title = {Ggcorrplot: {Correlation} {Matrix} {Heatmap} in {R} with
    Ggplot2},
  date = {2026-07-10},
  url = {https://www.datanovia.com/learn/biostatistics/correlation/ggcorrplot-in-r},
  langid = {en}
}
For attribution, please cite this work as:
“Ggcorrplot: Correlation Matrix Heatmap in R with Ggplot2.” 2026. July 10. https://www.datanovia.com/learn/biostatistics/correlation/ggcorrplot-in-r.