Introduction to ggcorrplot

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.

library(ggcorrplot)

The inputs: a correlation matrix and its p-values

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.0

To 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+00

ggcorrplot() 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).

Basic correlogram

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.

ggcorrplot(corr)
ggcorrplot(corr, method = "circle")

When method = "circle", circle.scale tunes the circle sizes for your output device.

ggcorrplot(corr, method = "circle", circle.scale = 0.6)

Reorder by clustering

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()).

ggcorrplot(corr, hc.order = TRUE, outline.color = "white")

hc.rect then draws rectangles around the clusters obtained by cutting the tree into k groups — a quick way to highlight the blocks.

ggcorrplot(corr, hc.order = TRUE, hc.rect = 3, outline.color = "white")

Layout: triangles and mixed glyphs

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.

Add the coefficients

lab = TRUE prints the correlation coefficient in each cell. lab_size, lab_col, and lab_fontface control its appearance.

ggcorrplot(corr, hc.order = TRUE, type = "lower", lab = TRUE)

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"
)

Mark statistical significance

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:

ggcorrplot(corr, hc.order = TRUE, type = "lower", p.mat = p.mat)

insig = "blank" hides them entirely:

ggcorrplot(corr, hc.order = TRUE, type = "lower", p.mat = p.mat, insig = "blank")

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:

ggcorrplot(corr, p.mat = p.mat, insig = "stars")

With lab = TRUE, the stars are appended to the coefficients instead (-0.85***), so one plot shows both the value and its significance:

ggcorrplot(corr,
  type = "lower", p.mat = p.mat,
  lab = TRUE, insig = "stars"
)

Colors and theme

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")
)

It is a ggplot: compose freely

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:

p <- ggcorrplot(corr, hc.order = TRUE, lab = TRUE)
ggplot2::ggsave("correlogram.png", p, width = 7, height = 6, dpi = 300)

Computing p-values with 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:

cor_pmat(mtcars, method = "spearman")

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)).

Session information

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