ggcorrplot draws
a correlation matrix as a ggplot2 plot. Because the
result is an ordinary ggplot object, you keep the full ggplot2
vocabulary: restyle it with a theme, add layers or annotations, and
compose it with other plots using +.
This vignette walks through the package end to end: computing the inputs, choosing a glyph and layout, reordering by clustering, adding the coefficients, marking statistical significance, and restyling the result.
Every plot starts from a correlation matrix. The examples use the
built-in mtcars data set.
data(mtcars)
corr <- round(cor(mtcars), 1)
corr[1:5, 1:5]
#> mpg cyl disp hp drat
#> mpg 1.0 -0.9 -0.8 -0.8 0.7
#> cyl -0.9 1.0 0.9 0.8 -0.7
#> disp -0.8 0.9 1.0 0.8 -0.7
#> hp -0.8 0.8 0.8 1.0 -0.4
#> drat 0.7 -0.7 -0.7 -0.4 1.0To mark significance later, you also need the matrix of correlation
p-values. cor_pmat() computes it with
stats::cor.test():
p.mat <- cor_pmat(mtcars)
p.mat[1:5, 1:5]
#> mpg cyl disp hp drat
#> mpg 0.000000e+00 6.112687e-10 9.380327e-10 1.787835e-07 1.776240e-05
#> cyl 6.112687e-10 0.000000e+00 1.802838e-12 3.477861e-09 8.244636e-06
#> disp 9.380327e-10 1.802838e-12 0.000000e+00 7.142679e-08 5.282022e-06
#> hp 1.787835e-07 3.477861e-09 7.142679e-08 0.000000e+00 9.988772e-03
#> drat 1.776240e-05 8.244636e-06 5.282022e-06 9.988772e-03 0.000000e+00ggcorrplot() never computes correlations itself — it
takes a matrix you already have. That means it works equally well with a
partial correlation matrix, a distance-derived similarity, or any other
square matrix in [-1, 1] (and, with
legend.limit = NULL, matrices outside that range such as a
covariance matrix).
The default encodes each correlation as a colored square.
method = "circle" encodes the value with the circle’s area
instead, which reads well when you want the magnitude to pop out.
When method = "circle", circle.scale tunes
the circle sizes for your output device.
Ordering the variables by hierarchical clustering brings correlated
variables next to each other, so the block structure becomes visible.
Set hc.order = TRUE; hc.method chooses the
linkage (as in hclust()).
hc.rect then draws rectangles around the clusters
obtained by cutting the tree into k groups — a quick way to
highlight the blocks.
For a symmetric matrix the two triangles carry the same information,
so you can show just one with type = "lower" or
type = "upper".
ggcorrplot(corr, hc.order = TRUE, type = "lower", outline.color = "white")
ggcorrplot(corr, hc.order = TRUE, type = "upper", outline.color = "white")A mixed layout draws a different glyph in each
triangle. Set lower.method and/or upper.method
to "square", "circle", or
"number"; the variable names are placed on the diagonal. A
common choice is the coefficients as numbers on one side and circles on
the other. Adding cell.grid = TRUE boxes every cell so the
glyphs sit in a tidy grid:
ggcorrplot(corr,
lower.method = "number", upper.method = "circle",
cell.grid = TRUE, show.legend = FALSE
)With "number", the coefficient text is colored on the
same scale as the fill, so values near zero fade out and the strong
correlations stand out.
lab = TRUE prints the correlation coefficient in each
cell. lab_size, lab_col, and
lab_fontface control its appearance.
Two options match common conventions for correlation tables:
leading.zero = FALSE drops the leading zero
(.85 instead of 0.85).nsmall keeps a fixed number of decimals
(e.g. nsmall = 2 shows 0.70).ggcorrplot(cor(mtcars[, 1:6]),
lab = TRUE, lab_size = 3.5,
leading.zero = FALSE, nsmall = 2,
type = "lower"
)Supplying p.mat lets the plot convey which correlations
are significant at sig.level (default 0.05).
There are three styles, chosen with insig.
insig = "pch" (default) crosses out the
non-significant cells:
insig = "blank" hides them
entirely:
insig = "stars" flips the emphasis:
rather than crossing out the non-significant cells, it marks the
significant ones with significance stars
(***, **, * for p <
0.001, 0.01, 0.05). With the default lab = FALSE this is a
standalone significance map:
With lab = TRUE, the stars are appended to the
coefficients instead (-0.85***), so one plot shows both the
value and its significance:
colors sets the diverging gradient. A length-3 vector
maps to low / mid / high; any longer vector (for example an 11-color
palette) is spread evenly across the scale.
ggcorrplot(corr,
hc.order = TRUE, type = "lower", outline.color = "white",
colors = c("#6D9EC1", "white", "#E46726")
)ggtheme swaps the base ggplot2 theme, and
tl.cex, tl.col, tl.srt style the
variable-name labels. legend.limit controls the color-scale
range — set it to NULL to use the data range, which is what
you want for a covariance matrix.
ggcorrplot(corr,
hc.order = TRUE, type = "lower",
ggtheme = ggplot2::theme_minimal,
tl.col = "gray30", tl.srt = 90,
colors = c("#003C67", "white", "#8F2727")
)Because ggcorrplot() returns a ggplot object, you refine
it with the usual ggplot2 grammar — titles, captions, and any additional
layer:
library(ggplot2)
ggcorrplot(corr, hc.order = TRUE, type = "lower", outline.color = "white") +
labs(
title = "Correlations among mtcars variables",
caption = "Hierarchically clustered"
) +
theme(plot.title = element_text(face = "bold"))The fixed 1:1 aspect ratio comes from coord_fixed(). To
let the cells fill the plotting area — useful with many long variable
names — use coord.fixed = FALSE (or add your own
+ coord_*, since the last coordinate system wins).
Saving works the same as any ggplot:
cor_pmat()cor_pmat(x, ...) returns the symmetric matrix of
p-values from stats::cor.test(), and passes
... through to it — so you can, for example, request a
Spearman test:
The use argument controls how missing values are handled
when deciding which cells are NA, mirroring
stats::cor(): the default
"pairwise.complete.obs" tests every pair with enough
overlapping observations, while "everything" sets a pair to
NA as soon as either variable has a missing value (so its
NA pattern lines up with cor(x)).
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggcorrplot_0.2.0.9000 ggplot2_4.0.3 rmarkdown_2.31
#>
#> loaded via a namespace (and not attached):
#> [1] vctrs_0.7.3 cli_3.6.6 knitr_1.51 rlang_1.3.0
#> [5] xfun_0.60 stringi_1.8.7 otel_0.2.0 S7_0.2.2
#> [9] jsonlite_2.0.0 labeling_0.4.3 glue_1.8.1 buildtools_1.0.0
#> [13] plyr_1.8.9 htmltools_0.5.9 maketools_1.3.2 sys_3.4.3
#> [17] sass_0.4.10 scales_1.4.0 grid_4.6.1 evaluate_1.0.5
#> [21] jquerylib_0.1.4 fastmap_1.2.0 reshape2_1.4.5 yaml_2.3.12
#> [25] lifecycle_1.0.5 stringr_1.6.0 compiler_4.6.1 RColorBrewer_1.1-3
#> [29] Rcpp_1.1.2 farver_2.1.2 digest_0.6.39 R6_2.6.1
#> [33] magrittr_2.0.5 bslib_0.11.0 withr_3.0.3 tools_4.6.1
#> [37] gtable_0.3.6 cachem_1.1.0