Introduction
两个数值变量看散点图就够了。但 10 个变量有 45 对关系,30 个变量有 435 对——逐一画散点做不到。
相关性热图把所有两两相关系数压进一个矩阵,用颜色表达方向和强度。它是探索入口,不是结论:先用它发现哪里可能有结构,再回到具体变量对上画散点、查离群点、做模型。
读的时候按这个顺序:
- 先看方向。 一端是负相关、另一端是正相关,中间色接近 0。
- 再看深浅。 越深,相关系数绝对值越大。
- 看色块。 聚类排序后相似的变量被排到一起,会出现连续的块状结构。
- 看星号。 它说的是"在这个样本量下相关性是否显著偏离 0",不是效应大小。强相关看颜色,显著性看星号,两件事。
- 回到散点图。 对最要紧的那几对变量一定要再画散点确认——热图只压缩,不解释。
Example Data
相关性热图要的输入是:3 个以上数值变量,同一批样本,一行一个样本。变量必须是数值——分类变量编码成 1/2/3 也能算出系数,但那个数没有意义。
mtcars 是 32 辆车的 11 项指标。这里取其中 10 个连续或近似连续的变量:mpg 和 wt、disp、hp、cyl 明显负相关——这不能读成"重量导致油耗降低",只能说这组变量和燃油效率有系统性的反向变化。
# 公开地址,和数据集页上「下载 CSV」给的是同一个 —— 不写仓库相对路径:
# 那个目录不进仓库,读者 clone 下来也没有这个文件,这段代码就跑不了
d <- read.csv("https://assets.evanzhou.org/tessera/csv/mtcars.csv")
vars <- c("mpg", "cyl", "disp", "hp", "drat", "wt", "qsec", "vs", "am", "gear")
df <- d[, vars]
cor_mat <- cor(
df,
use = "pairwise.complete.obs", # 有缺失时逐对使用完整观测,不整行删掉
method = "pearson" # 线性相关;变量分布很偏时改 "spearman"
)
星号来自 p 值,得单独算一张同样大小的矩阵。这个函数只依赖 base R:
cor_pmat <- function(x, method = "pearson") {
mat <- as.matrix(x)
n <- ncol(mat)
p <- matrix(NA_real_, n, n)
# 行列名必须跟着带上:下面两种画法都靠名字对齐,靠位置迟早错位
colnames(p) <- rownames(p) <- colnames(mat)
for (i in seq_len(n)) {
for (j in seq_len(n)) {
p[i, j] <- cor.test(mat[, i], mat[, j], method = method)$p.value
}
}
p
}
p_mat <- cor_pmat(df)
Palettes
相关系数有明确方向,所以必须用发散配色:一端负相关、另一端正相关,而中性的那一档必须正好落在 0 上。锚点偏了,读者会把"没关系"读成"弱正相关"——这是配色能造成的最实质的一种误读。
用的是 walter_white(冷蓝 → 冷白 → 深橄榄)。换成红蓝也行,关键不是挑哪两个色,而是读者能不能一眼分清负、零、正三个位置。
library(biopalette)
# 五色发散色板里取两端和正中间三个锚点:低 / 中 / 高
cor_cols <- get_palette("walter_white", type = "diverging")[c(1, 3, 5)]
Recipe
| No. | Method | Input Data | Palettes |
|---|---|---|---|
| 1 | corrplot |
cor_mat / p_mat |
cor_cols |
| 2 | ggcorrplot |
cor_mat / p_mat |
cor_cols |
1 · corrplot
一个函数出图,聚类排序、图例、星号都在里面。改颜色改图例不用翻文档。
#| fig: corrplot
#| fig-width: 8
#| fig-height: 6
library(corrplot)
corrplot(
corr = cor_mat,
method = "color", # 用色块填格;也可以是 circle / square / number
type = "full", # 画满;upper / lower 只画一半,省掉重复信息
order = "hclust", # 按层次聚类重排变量 —— 块状结构就是这么来的
hclust.method = "complete", # 聚类的连接方式,影响分块边界怎么切
col = colorRampPalette(cor_cols)(200),
col.lim = c(-1, 1), # 固定色标范围,否则色深随数据变、两张图没法比
addgrid.col = "white", # 格线颜色,白线让色块之间断开
cl.pos = "r", # 色标(colour legend)放右边
cl.ratio = 0.15, # 色标占整图宽度的比例
tl.pos = "l", # 变量名(text label)只放左边,不重复放上面
tl.col = "black",
tl.srt = 0, # 标签旋转角度,0 = 水平
p.mat = p_mat, # 传入 p 值矩阵,星号才画得出来
sig.level = c(0.001, 0.01, 0.05),
insig = "label_sig", # 不显著的格子留白;改成 "blank" 会整格涂掉
pch.cex = 0.9, # 星号大小
pch.col = "black",
title = "Correlation Matrix of mtcars",
mar = c(0, 0, 1.8, 0) # 上边留白,否则标题会被裁掉(base graphics 的老问题)
)
