R语言cor()函数全解析:从相关系数计算到缺失值处理与显著性检验
2026/9/10 10:01:09 网站建设 项目流程

相关系数这玩意儿,几乎是数据分析里天天都要打交道的东西。不管你是做生物信息、金融风控,还是社科问卷分析,只要想量化两个变量之间的线性关系,你大概率绕不开R语言的cor()函数。说实话,我见过不少人在这一步上翻车,有的是分不清几种相关系数的区别,算出来的结果解释得完全不对;有的是没搞定缺失值处理,一列数据里有NA结果就直接报错或者给个莫名其妙的结果;还有的是只算出了相关系数,却不知道这玩意儿还分正负、绝对值多大才有意义。

用惯了之后你会发现,cor()确实像一个瑞士军刀,但用之前你得先搞清楚它的刀片怎么换。这篇文章我就把自己这些年用cor()函数的经验完整的梳理一遍,包括底层计算逻辑、三个核心参数、相关矩阵批量分析、缺失值处理、显著性检验、可视化锦上添花,以及实操中的各种坑和排查技巧。看完你不仅能熟练算出相关系数,还能知道什么时候该用哪种方法、怎么解读结果、怎么避开数据里的坑。

1. 相关系数的底层逻辑:从皮尔逊到非参数

1.1 三种相关系数怎么选

cor()函数支持三种相关系数计算,对应method参数的三个选项:"pearson""spearman""kendall"。知道它们怎么选,是正确解读数据的硬门槛。

大家最常用的是Pearson相关系数。它的逻辑简单直接:把两组数据分别标准化后,算它们之间的协方差。公式是分子为X与Y的离均差乘积之和,分母为各自离均差平方和开根号后的乘积。说白了,Pearson衡量的是两个变量是否沿着一条直线同向或反向变动。它对异常值极其敏感,一个离群点就能让相关系数从0.8跌到0.2甚至变号,因为离均差在平方之后被放大了。

Spearman等级相关系数则是先把原始数据排名次,再对排名后的数据计算Pearson相关。因为排名对原始数值的变化不敏感,只关心大小顺序,所以异常值对它的影响要小得多。比如有一组学生的成绩数据,某次考试有个同学作弊得了满分100,旁边一个同学因为身体不适只考了20分,如果你用Pearson去衡量成绩和课后练习时间的关系,这个异常值就会严重拉低相关系数;但换成Spearman,满分那个学生排名还是第一,身体不适的学生排名还是垫底,整体秩序没变,系数就不会受到太大冲击。这就是为什么碰到明显有极端值、数据明显偏离正态分布、或者变量本身是等级资料(比如满意度1到5星)时,优先考虑Spearman。

Kendall's tau是另一种基于等级的思路,但逻辑不太一样。它是把所有的样本点两两配对,看看有多少对在X和Y上的变化方向一致(一致对),多少对方向相反(不一致对),然后用一致对与不一致对的差值除以总对数。这个方法的稳健性比Spearman还要好,在小样本(比如n小于30)和存在大量平局(比如很多样本的排名相同)的情况下,Kendall的估计往往更稳定。不过它也牺牲了一些效率,算起来会比Spearman稍微慢一点,而且解释上没有Spearman直观。

按我的经验,选型逻辑大致是这样:如果你能确认数据近似线性关系、无显著异常值、变量为连续数值型,用Pearson,因为它统计功效最高;如果数据里有甩尾的极端值,或者变量测的是顺序等级资料,换Spearman没错;如果样本量很小,比如只有十几个观测,而且你担心平局影响估计,Kendall是保底的选择。

1.2 相关系数到底该怎么解读

算出相关系数之后,最怕的就是只看数值不加思考。一个0.7的相关,和一个-0.7的相关,彼此的强度完全一样,差的只是方向。正相关表示X增大时Y也倾向增大,负相关表示反向变动,而0意味着没有线性关系——注意,是没有线性关系,不代表没有关系。数据如果沿着一条完美的抛物线走,X和Y的Pearson相关系数很可能接近0,但其实它们之间存在着极强的非线性关联。

绝对值的大小划分,在统计教科书中通常这样认为:0.8以上算强相关,0.5到0.8算中等相关,0.3到0.5算弱相关,0.3以下通常认为相关性很弱。但这个界限只是启发式经验,具体领域差别极大。在物理实验中,相关系数往往要到0.999以上才合格;而在社会科学问卷里,两个题项之间能达到0.5的相关就已经是很有价值的结果了。更关键的一点是,永远不要忘了看p值。相关系数只告诉你关系的强弱和方向,显著性检验才告诉你这个相关性是不是随机波动造成的。计算一个0.2的相关系数,如果你只有10个样本,置信区间里可能包含了0,这个结果基本没有说服力;但如果是1000个样本,0.2的相关可能已经极其显著了。所以看到相关系数之后,下一步几乎总是去计算它的显著性检验,这个下文会专门展开。

2. cor()函数的三个关键参数:x、y与use

2.1 x和y的结构决定输出形态

cor()函数的最基本用法是cor(x, y = NULL, method = c("pearson", "kendall", "spearman"))。当你只传入一个x时,x必须是一个数据框或矩阵,函数会计算这个数据框里所有数值变量两两之间的相关系数矩阵;当你同时传入xy时,xy既可以是向量,也可以是矩阵或数据框,函数会计算它们之间的交叉相关矩阵。

这里有个细节很多人容易忽略:x是一个数据框时,里面含有非数值类型的列会直接报错,报错信息类似'x' must be numeric。解决办法是先筛选出数值列,或者使用dplyr::select_if(is.numeric)这类工具。我经常遇到的情况是,数据框里有一列是ID编号,看起来是数值,但实际意义是分类标签,这种列混入相关系数矩阵后会计算出毫无意义的相关。所以算矩阵之前,一定要自己检查一下哪些列才是真正意义上的连续数值变量。

当你传入y参数时,数据结构决定了输出矩阵的形状。比如x是一个n行3列的数据框,y是一个n行2列的数据框,cor(x, y)会返回一个3行2列的矩阵,行对应x的变量,列对应y的变量。这个特性在做多个特征与一个目标变量的相关性分析时非常方便,不用写循环,一句话就能得到特征与目标之间的所有相关系数。

2.2 use参数的四种缺失值处理策略

缺失值是实际数据里躲不掉的敌人。cor()函数默认的use参数是"everything",这个选项不允许任何缺失值存在,只要xy中有任意一个NA,结果就直接返回NA。这当然不是你想要的,于是有了另外几种处理方式。

  • use = "complete.obs":这是行删除法,也叫按列表删除法。它会先删掉数据中所有含有缺失值的行,再对剩下的完整行计算相关系数。优点是简单直观,不同相关系数之间比较时使用的基础数据一致,没有偏误;缺点是一旦缺失值分散在很多行,剩余的有效样本量会被大幅削减,甚至丢了一半以上的数据,信息浪费严重。

  • use = "pairwise.complete.obs":这是成对删除法,也是我日常项目中最常用的选项。它逐对计算相关系数时,只使用那一对变量中都没有缺失值的行。比如A列只有第1到50行有值,B列只有第1到40行和第60到70行有值,那么计算A与B的相关时,只会使用第1到40行的完整数据。这种方法最大化了样本利用率,每个相关系数都尽量使用更多数据。但它有个隐藏的坑:不同变量对计算时用的样本量不一样,导致整个相关系数矩阵的每个元素基于不同样本,矩阵内部的一致性打了折扣。而且用这种矩阵做后续的多元分析,比如求逆矩阵或做主成分分析,有时候会算出违背数学常识的结果,比如特征值出现负数。

  • use = "na.or.complete":这个选项和complete.obs逻辑一样,唯一的区别是当数据没有缺失值时,它不会隐藏地转换类型,而是直接返回正常结果;如果数据完全没有完整行,则返回NA。平时用到的场景不多,但如果你写了一个通用函数给其他人用,为了避免数据恰好没有缺失值时出现异常行为,可以加上这个选项。

给新手一个建议:如果你只是想快速看看变量间的大致关系,用pairwise.complete.obs最省事;如果你后续还要基于相关矩阵做进一步建模,比如结构方程或者主成分分析,最好用complete.obs,确保矩阵内所有相关系数基于同一组样本,牺牲点样本量,换数学性质上的干净。

2.3 method参数的执行细节

虽然前面已经讲了三种相关系数的选型逻辑,但这里补几个执行层面的细节。method参数不区分大小写,"pearson""Pearson""PEARSON"都可以识别。另外,你只能指定一种方法,method = c("pearson", "spearman")这种写法会被当作向量传入,函数内部只会用第一个值,后面的会被静默忽略,这个行为容易让人困惑但实际影响不大,因为你本来就应该一次用一种方法。

另外,这三个相关系数对数据的假设前提不同。Pearson要求两个变量近似服从正态分布,当然这不是绝对要求,但偏离太远时估计的可靠性会下降;Spearman和Kendall没有分布假设,它们是纯粹的秩次信息统计量。所以在做数据探索时,我习惯先把数据分布快速看一眼,用hist()画个直方图或者用summary()看分位数,如果看到明显的长尾或极端值,就直接切到Spearman。

3. 一次算完所有变量的相关系数矩阵

3.1 从数据框直接生成矩阵

实际项目里很少只分析两个变量之间的关系,更多时候需要同时分析几十个变量的两两关联。这种场景下,一句cor(numeric_df, use = "pairwise.complete.obs", method = "spearman")就能生成一个完整的相关矩阵。矩阵的行和列都是原数据框的变量名,对角线恒为1,上下三角对称。

得到相关矩阵之后,很多人只盯着数字一个个看,效率极低。我常用的方法是把矩阵转成数据框,然后用dplyr筛选出绝对值大于某个阈值(比如0.7或0.8)的变量对,快速定位哪些变量存在高度共线性。比如在做回归建模之前,检查自变量之间的相关矩阵,如果发现某两个自变量的相关系数超过0.9,那基本可以判定存在多重共线性,需要删除其中一个或者使用正则化方法。

处理完矩阵之后,可视化成热力图几乎是必做的一步。安装corrplot包之后,一行corrplot(cor_mat, method = "circle", type = "upper", addCoef.col = "black")就能把相关矩阵画成圆形热力图,颜色深浅映射相关系数大小,蓝色系代表正相关,红色系代表负相关。这个图在报告里特别吃香,印刷到论文里档次也够。ggcorrplot包则是基于ggplot2体系的可视化方案,如果你习惯ggplot2的语法,用ggcorrplot::ggcorrplot(cor_mat, lab = TRUE)几乎零成本上手。

3.2 一次提取核心变量对的相关系数

计算完整矩阵之后,如果你想单独提取某个变量对的相关值,可以用下标直接取,比如cor_mat["height", "weight"]。但更优雅的方式是用reshape2::melt()或者tidyr::pivot_longer()把矩阵转换为长格式的三列数据,变量1、变量2、相关系数。转换之后,就可以方便地排序、筛选,找出数据集中最值得关注的若干相关关系。

我自己的一个标准操作是:

library(tidyr) cor_df <- as.data.frame(cor_mat) cor_df$var1 <- rownames(cor_df) cor_long <- pivot_longer(cor_df, cols = -var1, names_to = "var2", values_to = "corr") cor_long <- cor_long[cor_long$var1 != cor_long$var2, ] cor_long <- cor_long[order(-abs(cor_long$corr)), ] head(cor_long, 20)

这段代码先剔除了对角线上的自相关,然后按相关系数绝对值降序排列,取前20条最强的相关对。这样一眼就能看到数据里最突出的关系,比盯着矩阵方格找要快得多。

4. 数据不完美时的应对策略

4.1 异常值和缺失值的双重考验

实际数据永远比教科书干净得多。我记得有一次分析一批基因表达数据,整体来看有两个基因的表达量相关系数很高,但把散点图一画出来,发现右上角有个样本点像钉子一样钉在那里,把本来没有线性关系的点群硬生生拉出了正相关。这就是异常值的威力。遇到这种情况,解决方法有两个方向:一是用winsorize(缩尾处理),把极端值压缩到某个分位数的边界,比如把超过95%分位数的值全部设为95%分位数的值;二是直接改用Spearman相关系数,因为秩次转换天然降低了异常值的影响力。

缺失值方面,除了前文讲的complete.obspairwise.complete.obs之外,还有一种更精细的处理方式:先做数据填补,再计算相关。常用的填补方法有中位数填补、多重插补(mice包)和K近邻填补(DMwR包或caret包里的preProcess函数)。填补之后再算相关,可以避免成对删除导致的样本量不一致问题。但填补方法本身带有假设,填出来的数据并不等于真实值,所以填补后的相关系数也会带有偏差,实际操作时需要对填补的影响心里有数。

4.2 非线性关系与分层样本的处理

遇到非线性关系时,计算标准的线性相关系数很容易得出误导性的结论。比如一个U形关系,两端的样本拉高了方差,但中间区域的中等值反而让整体线性趋势趋近于零。此时如果只有两个变量且怀疑非线性,可以先画散点图加geom_smooth()看看趋势曲线,再用lm(y ~ poly(x, 2))看看二次项是否显著;如果你只是想给非线性关系的强度做一个度量,可以把两个变量都进行rank()变换后再算Spearman,也可以考虑距离相关性(distance correlation),R里面有energy包实现这个计算。

另一个常见场景是分层样本。比如全校学生的数据里头,高一和高二的成绩关系模式完全不同,混合在一起算相关系数,可能得出一个不高不低的数值,掩盖了各层内部的真实关系。这时候正确的做法是先分组计算相关系数,看层内的相关模式是否一致,再决定要不要报告总的相关系数。用dplyr::group_by()summarise()可以很轻松地实现分组计算。

5. 相关性检验:相关不等于因果,但也要有证据

5.1 cor.test()函数的使用方法与参数

计算相关系数只是第一步,想要确认这个相关性不是抽样误差造成的,得靠假设检验。R语言里的标准函数是cor.test(),它比cor()多做几件事:输出相关系数的置信区间、p值以及检验所用的统计量和方法。

基本用法是:

cor.test(x, y, method = "pearson", alternative = "two.sided", conf.level = 0.95)

xy必须是两个等长的数值向量,不能传入多列数据框。cor.test()的输出会给出一个t统计量以及p值,p值小于0.05通常认为存在统计显著的线性相关。alternative参数可以选择单尾检验,如果你有明确的先验方向,比如预期X与Y只会正相关,可以用alternative = "greater",这样检验功效更高一些。

Spearman检验的实现是cor.test(x, y, method = "spearman"),它基于秩次的检验统计量是S,Kendall检验的统计量是tau。这两个检验都不需要正态分布假设,在分布偏离严重时比Pearson检验更加稳健。

5.2 批量计算多对变量的p值

在做探索性分析时,变量数量多,cor.test()每次只能处理一对变量,效率很低。这时候有两个思路:一个是用psych包里的corr.test()函数,它能一次对整个数据框的所有变量两两计算相关系数矩阵和p值矩阵;另一个是写一个两层for循环或者用apply系列函数自行批量处理。

psych::corr.test(data, method = "pearson", adjust = "bonferroni")是我最常用的批量方案。值得注意的adjust参数,在同时做几十对变量检验时,单纯看原始p值很容易出现假阳性。比如你做了100次检验,即便这些检验全部都是零相关,也会有大约5次因随机波动而超过0.05显著性阈值。所以批量检验时应该做多重校正,最保守的是"bonferroni",它将阈值除以检验次数;更温和一些的是"fdr"(BH法),适用于变量多、允许一定比例假阳性的探索场景。

5.3 置信区间怎么解读

cor.test()输出里的置信区间是很容易被忽略但极有价值的信息。它反映了相关系数估计的精度:样本量越小,置信区间越宽;样本量越大,区间越窄。一个点估计为0.6的相关,如果是基于20个样本算出来的,置信区间大概会从0.2跨到0.83,用它做决策很冒险;但如果样本量到了200,同样的点估计置信区间可能只有0.5到0.69,结果就可靠得多。

6. 实操中那些容易翻车的细节与排查技巧

6.1 常见报错的原因与解决方案

cor()函数的报错信息往往不够直接,我这里把几个高频报错汇总成一张速查表,方便大家对照排查。

报错信息常见原因解决办法
'x' must be numeric数据框含有字符列或因子列先用select_if(is.numeric)筛选数值列
incompatible dimensions传入的xy行数不一致检查两个数据框的行数是否相同
missing observations in yy中包含NA值且useeverything使用pairwise.complete.obs或先清理缺失值
NA/NaN/Inf in foreign function call数据中包含InfNaN先用is.finite()筛选数据
method must be one of...拼错了方法名检查拼写,确保是pearson/spearman/kendall三个之一

碰到Inf的情况不怎么常见但一旦出现就特别难受。数据清洗时如果用了除零操作,比如计算基因表达量的比值时某一行分母为0,就会产生Infcor()函数遇到它直接罢工。解决办法是在计算之前用is.finite()把非有限值过滤掉。

6.2 样本量与置信区间的微妙关系

样本量对相关系数的影响比很多人想象的更大。统计学里有一个重要的直觉:当总体相关系数为0时,样本相关系数的标准差是1除以根号下n-2左右。这意味着如果n只有10,抽样误差很大,你算出来的相关系数可能是±0.5以上仅仅因为随机波动;但如果n等于500,同样的随机波动范围和±0.1都够不上。所以看到高相关系数时,第一反应不应该是“哇,关系好强”,而是“这个样本量够不够支撑这个结论”。

实际项目中,我有一个从样本量角度出发判断相关可靠性的经验:指数级的规律是,要想让置信区间的宽度不超过±0.1,所需的样本量大约在400左右;要将置信区间压缩到±0.05,大致需要1600个样本。如果样本量不足,建议报告中除了点估计之外,务必同时报告置信区间,避免读者被单个数字误导。

6.3 分组分析里的一个隐藏坑

还有一种翻车场景是在分层数据中直接计算总体相关。比如研究时间投入与成绩的关系,数据里包含文科班和理科班,两个班内部的相关方向可能完全相反:理科班投入时间越多成绩越好,文科班投入时间越多成绩反而越差,但合并到一起算总相关时,两端互相抵消,总相关系数可能趋近于0,导致你得出“时间投入与成绩无关”的错误结论。这个现象在统计上叫Simpson悖论的一个变体。所以,在计算相关矩阵之前,先用group_by结合summarise或者facet_wrap按类别分别画出散点图和相关线,观察层内模式是否一致。如果发现层间方向不一致,正确的做法是在报告中分列注明,而不是给一个简单的总体相关性。

6.4 与cor()配合的三个高效小技巧

最后分享几个我自己日常使用cor()时的小技巧,效率提升很明显。

第一个是使用dplyr::across()结合cor()批量计算多个目标变量的相关。假设你想知道一个数据框里后面5列分别与第一列的相关性,可以这样写:

df %>% summarise(across(2:6, ~ cor(df[[1]], .x, use = "complete.obs")))

这个写法简洁且可读性好,后续改某个变量范围也很方便。

第二个技巧是合并相关系数与显著性结果为一个理解友好的表格。用psych::corr.test()同时拿到相关系数矩阵和p值矩阵之后,写个小函数把两个矩阵合并输出为长格式:

library(tidyr) stat_list <- psych::corr.test(df[sapply(df, is.numeric)], adjust = "fdr") r_df <- as.data.frame(stat_list$r) p_df <- as.data.frame(stat_list$p) # 整理成 "变量对 | r | p" 的表格

这样整理出来的表格放在报告里非常直观,审稿人和领导看了都不需要再自己找输出。

第三个技巧是注意cor()计算结果的对称性问题。因为数值计算的底层精度原因,cor_mat[1, 2]cor_mat[2, 1]在极少情况下可能存在微小的浮点差异,比如0.123456789与0.123456788。在做严格的一致性检查时,可以顺手round()一下,或者用identical()加容忍度来判断。

我自己在项目里积累的经验是:相关系数从来不是分析的终点,而是探索的起点。它告诉你哪些变量值得深挖,哪些关系可能值得建模,哪些变量间存在冗余需要降维。真正做出可靠结论,还需要结合散点图、显著性检验、样本量评估以及领域知识,几个环节缺一不可。希望这篇关于cor()函数的使用总结,能帮你少踩几个数据处理时的暗坑,把时间花在真正有洞察力的分析上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询