
相关系数这个系列写到第4篇了。前面几篇把皮尔逊相关系数翻来覆去讲了不少包括怎么算、怎么检验、怎么在论文里解释。今天把另一半补上——斯皮尔曼相关系数Spearmans rank correlation coefficient也就是很多人常说的“等级相关系数”。先说清楚这东西能干嘛。你在数学建模比赛里拿到一批数据想判断两个变量之间有没有关系但画完散点图发现根本不是直线趋势或者数据里有几个明显的离群点这时候皮尔逊相关系数就会给你一个“不太可信”的数字。斯皮尔曼相关系数解决的正是这类问题它不关心数据的具体取值只关心排序所以对非线性关系更包容对异常值更稳健。这篇文章我会从原理讲到Python实现再结合数学建模竞赛的实际场景聊聊怎么选、怎么写进论文最后把我踩过的坑一并列出来。适合正在备战国赛、华为杯的参赛选手也适合做数据分析时需要对变量关联性做判断的读者。1. 为什么需要斯皮尔曼相关系数1.1 皮尔逊的硬门槛数据得“长”成什么样想用好一个工具得先知道它的前提条件。皮尔逊相关系数Pearson correlation coefficient衡量的是两个变量之间的线性相关程度它默认数据满足几个条件近似线性关系、双变量近似正态分布、没有明显异常值。问题在于数学建模拿到的数据往往没这么“听话”。经济数据、人口数据、评分数据常常是偏态的变量之间的关系也可能是指数型、对数型、幂函数型画出来是条弯弯曲曲的曲线再加上数据采集过程中偶尔混入的脏数据一个异常值就可能把皮尔逊系数从0.8拉到0.3。用皮尔逊之前你得花大量时间做正态性检验、线性诊断、异常值处理一套流程下来光是数据预处理就够喝一壶的。我见过不少参赛选手强行对不满足条件的数据计算皮尔逊系数还煞有介事地标注显著性。说实话评委里懂统计的老师不少这种做法容易被一眼看穿。更合理的做法是数据不满足皮尔逊的适用条件时直接换用斯皮尔曼相关系数。1.2 斯皮尔曼的核心思想不看数值看排名斯皮尔曼相关系数的思路很有意思既然数值本身的分布有各种问题那我干脆不看数值只看排名。它的另一个名字“等级相关系数”也由此而来。比如有5个学生的数学成绩和物理成绩哪怕数学满分是100分、物理满分是150分量纲完全不同都没关系。只要我把两门成绩分别从高到低排序然后看每个学生在两门学科中的排名是否一致——排名同步上升就说明两门成绩存在正向关联。这个“基于排名”的设计让斯皮尔曼相关系数天然具备了两个优点。第一对异常值稳健得很你数据里混进一个离群点最多让它自己的排名变一下对整个排序格局影响很小。第二不需要假设数据符合正态分布因为秩排名本身就是均匀分布的和原始数据的分布形态没有关系。用生活化的类比来说皮尔逊关心的是“身高和体重是不是同比例增长”而斯皮尔曼关心的是“个子最高的人是不是体重也最重”。前者看的是具体数值的线性关系后者看的是等级次序的一致性。很多时候我们做相关性分析的目的是想回答“变量之间是否存在关联趋势”而不仅仅是“是否存在精确的线性关系”这时候斯皮尔曼反而更贴近分析的本意。2. 斯皮尔曼相关系数的数学原理与计算2.1 秩与等级先把数据翻译成排名斯皮尔曼的英文原名是Spearmans rank correlation coefficient这里的rank就是“秩”的意思。所谓秩就是把一组数据从小到大或从大到小排序后每个数据所在的位置编号。比如数据组 [60, 85, 72, 95, 45]从小到大排序后是 [45, 60, 72, 85, 95]那么原始数据对应的秩分别是 [2, 4, 3, 5, 1]。有并列值时怎么办比如两个学生的成绩都是85分排第2还是第3统计学里的常用做法是取平均秩也就是原本第2和第3两个位置两个85分都取2.5。Python的pandas里的rank方法默认就是这么做methodaverage这也是竞赛中最推荐的方式。有些编程实现会用methodmin或者max排序逻辑会不同计算出来的相关系数也可能有细微差别所以复现别人的代码时一定要留个心眼。2.2 两种计算公式到底怎么选斯皮尔曼相关系数的计算有两种方式。第一种是简化公式经典教材里的写法是rs 1 - (6 * Σdᵢ²) / (n * (n² - 1))其中dᵢ是第i个样本在两个变量中的秩之差n是样本量。这个公式看起来简洁好记但我必须提醒一句它只适用于没有并列等级的情况而且对样本量也有一定要求。只要数据里出现并列值这个公式得到的结果就和严格定义有偏差。我自己刚学那会儿就踩过这个坑拿着简化公式手算了一组含并列值的数据结果和统计软件输出的不一致查了半天才明白原因。第二种是通用定义也是最稳妥的实现思路把两个变量分别转化为秩然后对秩计算皮尔逊相关系数。因为秩本身就是数值所以完全可以直接套用皮尔逊公式rs cov(Rx, Ry) / (std(Rx) * std(Ry))这里的Rx和Ry是原始变量各自的秩序列。这种方法天然兼容并列等级的情况因为并列值已经通过平均秩处理好了。实际操作中我建议一律采用第二种思路跑出来的结果和scipy等统计库完全一致也不容易出错。2.3 手算一遍5个学生的例子光讲公式太抽象我们实际算一遍。假设5个学生的数学和物理成绩如下学生数学成绩物理成绩数学排名物理排名dd²A95881100B807523-11C72783211D60554400E45505500数学成绩从高到低排名A第1B第2C第3D第4E第5。物理成绩排名A第1C第2B第3D第4E第5。计算每个学生的秩之差d得到d²的平方和为2。代入简化公式rs 1 - (6 * 2) / (5 * (25 - 1)) 1 - 12 / 120 0.9rs 0.9说明数学和物理成绩存在很强的正相关关系。观察数据也能发现除了B和C两个同学的物理排名互换了一下其余同学的排名顺序基本一致整体趋势是成绩好的学生两门都强这就是高度正向关联的直观体现。这个案例里没有并列值简化公式的结果没问题。但如果你要对含并列值的数据手算建议先做平均秩处理再按秩序列计算皮尔逊系数这样最接近统计软件的输出。3. Python实操与代码实现3.1 最省事的方案scipy.stats.spearmanr写数学建模代码我习惯直接上scipy一行代码出结果。spearmanr函数的用法很简单import numpy as np from scipy import stats math_scores np.array([95, 80, 72, 60, 45]) phys_scores np.array([88, 75, 78, 55, 50]) rho, p_value stats.spearmanr(math_scores, phys_scores) print(f斯皮尔曼相关系数: {rho:.4f}) print(fp值: {p_value:.4f})运行结果中rho就是斯皮尔曼相关系数p值对应显著性检验的结果。p值小于0.05通常认为存在显著相关小于0.01则说明相关性极显著。实际处理DataFrame数据时可以用类似下面的方式一次性计算多列之间的相关系数矩阵import pandas as pd df pd.DataFrame({ math: math_scores, phys: phys_scores, chem: [92, 85, 80, 65, 48], bio: [82, 78, 70, 58, 52] }) # 计算相关系数矩阵 corr_matrix df.corr(methodspearman) print(corr_matrix)pandas的corr方法里直接指定methodspearman就行返回的矩阵在论文里稍微格式化一下就能用非常方便。3.2 手写一个斯皮尔曼计算器pandas实现虽然说现成的库好用但我一直建议你在比赛前手写一遍核心代码不是为了造轮子而是为了真正理解底层逻辑。万一遇到特殊情况需要调整算法你才知道从哪里下手。手写实现的核心就是“先排名再算皮尔逊”import pandas as pd import numpy as np def spearman_manual(x, y): # 转为pandas Series方便调用rank rx pd.Series(x).rank(methodaverage).values ry pd.Series(y).rank(methodaverage).values # 对秩序列计算皮尔逊相关系数 # 等价于 cov(rx, ry) / (std(rx) * std(ry)) rho np.corrcoef(rx, ry)[0, 1] return rho math_scores [95, 80, 72, 60, 45] phys_scores [88, 75, 78, 55, 50] rho_manual spearman_manual(math_scores, phys_scores) print(f手写斯皮尔曼相关系数: {rho_manual:.4f})输出结果应该和scipy一致都是0.9。这里的关键点是rank方法默认采用average策略正好对应平均秩的处理方式。如果你用的是methodmin遇到并列值时结果会完全不同这也是我之前踩坑的地方。3.3 可视化辅助判断单调关系动手算之前我强烈建议先画个散点图。这不是形式主义而是帮你快速判断两个变量之间的关系形态。如果散点图呈现明显的单调上升或下降趋势但中间有弯曲斯皮尔曼会给出比较高的相关系数如果图形是U型或者倒U型那就不是单调关系斯皮尔曼也会“失灵”需要尝试其他分析方法。import matplotlib.pyplot as plt plt.figure(figsize(6, 5)) plt.scatter(math_scores, phys_scores, s60, alpha0.7) plt.xlabel(数学成绩) plt.ylabel(物理成绩) plt.title(数学与物理成绩散点图) plt.grid(alpha0.3) plt.show()散点图可视化还有个好处能直观发现异常值。如果某个点明显偏离整体趋势别急着删先排查一下是不是录入错误。如果是真实数据中的极端情况斯皮尔曼的抗干扰能力足以应对这也正是它在竞赛数据预处理环节广受欢迎的原因。还有一点处理多变量数据时可以绘制相关矩阵热力图用seaborn的heatmap函数把斯皮尔曼矩阵可视化颜色深浅一眼就能看出哪些变量关联密切这对后续特征筛选很有帮助。4. 在数学建模竞赛中的实战应用4.1 赛题中“选谁”的决策流程国赛、华为杯或者美赛的题目经常要我们分析变量之间的关联性。拿到一组数据到底用皮尔逊还是斯皮尔曼我自己的判断流程是这样的判断点满足条件选择散点图是否呈直线趋势是皮尔逊双变量是否近似正态分布是皮尔逊数据中是否有明显异常值无皮尔逊变量关系是否单调但非线性是斯皮尔曼数据是否存在偏态或厚尾是斯皮尔曼数据是否为有序分类变量如评分、等级是斯皮尔曼数据是否含异常值或离群点是斯皮尔曼更保险的做法是两个都算一遍。如果结论一致说明关系稳健在论文里可以只报告一个如果结论不一致比如皮尔逊系数接近0而斯皮尔曼系数很高说明变量之间存在的是非线性单调关系这时以斯皮尔曼为准在论文里恰好可以作为一个分析亮点展开讨论。4.2 典型赛题场景与数据特点数学建模赛题里斯皮尔曼常见的应用场景可以归纳为三类。第一类是经济与社会科学数据比如分析“教育投入与地区经济增长”“人口老龄化与消费结构”这类问题。经济数据普遍存在偏态分布和异常值而且变量之间的理论关系往往不是精确线性用斯皮尔曼做相关性初筛非常合适。第二类是多指标综合评价比如一些评价模型在构建指标体系之前需要先检验指标之间的相关性避免信息重复。问卷量表、专家打分这类数据本质上是有序分类变量打3分和打4分之间的“距离”并不严格相等直接计算皮尔逊在统计上不够严谨斯皮尔曼才是贴合的。第三类是数据预处理阶段的多重共线性诊断。比如回归建模之前用斯皮尔曼计算相关系数矩阵如果发现两个特征之间rs超过0.8就要考虑保留其中一个或做降维处理。我之前参加强化学习类题目时就是靠斯皮尔曼筛掉了一批强相关特征后续模型收敛速度和稳定性都好了不少。4.3 论文里的标准写法与表述比赛论文跟实验报告还不一样评委每天要看几百页论文你的表述必须规范、清晰、有说服力。我建议按这个套路写。第一步说明为什么选斯皮尔曼。可以写“考虑到数据可能存在异常值且变量间关系未必严格线性本文采用斯皮尔曼相关系数进行相关性分析”。第二步给出计算公式或指明使用Python的scipy实现介绍计算方式。第三步用表格呈现结果。常见的做法是做一个相关系数矩阵并标注显著性水平。可以在相关系数后用星号标注*表示p0.05**表示p0.01这也是学术论文的通用惯例。第四步对关键结果做文字解读。比如“由表可知X1与X2的斯皮尔曼相关系数为0.73且通过显著性检验p0.01表明两者之间存在显著的正向关联”。注意不要把“相关”写成“因果”这是统计表述里的大忌。另外论文中如果同时使用了皮尔逊和斯皮尔曼最好明确说明两者结论是否一致若不一致则解释可能的原因例如数据中存在非线性单调关系或异常值影响这能体现你思考的深度也更容易获得评委的认可。5. 常见问题与避坑指南5.1 p值理解与样本量陷阱斯皮尔曼相关系数本身是好算的真正容易翻车的地方在显著性检验。很多人跑出rs0.6就急着下结论忽略了p值。注意rs0.6只能说明样本里存在中等强度相关性但样本太小的时候这个结果很可能只是随机波动p值会告诉你结论的可靠程度。scipy的spearmanr会直接返回p值计算原理是基于t分布近似。样本量比较大的时候比如n大于30这个近似效果很好样本量很小比如n小于10时p值可能不准确可以考虑查斯皮尔曼秩相关系数临界值表来辅助判断。竞赛中如果遇到小样本建议在论文里注明p值是通过近似方法计算的这样做更严谨。5.2 三种相关系数结果不一致怎么办皮尔逊、斯皮尔曼、Kendalls tau-b这三种相关系数在绝大多数情况下给出的结论方向一致但数值可能差异明显。如果出现皮尔逊系数接近0、斯皮尔曼系数却很高的情况通常说明两个变量之间是一种单调但非线性的关系比如指数增长、对数衰减等形式。斯皮尔曼能捕捉这种趋势皮尔逊则“看不见”。我处理这类问题的习惯是画出散点图确认趋势形态然后用斯皮尔曼量化关联强度最后在论文中把曲线拟合也做出来。这样做既能解释相关性的存在又能进一步刻画关系的形式逻辑链完整比单纯给一个相关系数强得多。5.3 实操中的几个隐蔽坑第一个坑是数据顺序错位。使用scipy的spearmanr时传入的两个数组对应顺序必须一致。尤其是从Excel或CSV里读数据后做了排序、去重等操作一不留神就把对应关系打乱了相关系数算出来完全没意义。建议用pandas的DataFrame管理数据筛选操作后重置索引。第二个坑是没有处理缺失值。spearmanr遇到缺失值返回的结果是nanpandas的corr默认会忽略缺失对但如果你把Series直接喂给scipy就得提前dropna。我的习惯是统一用df.dropna(subset[col1, col2])清洗后再计算。第三个坑是rank方法的选择。前面提过不同rank方法处理并列值的方式不同。用scipy计算时默认是平均秩手写实现时如果用methodmin两者结果不一致容易怀疑是代码写错了其实是对齐方式的问题。多变量大规模数据用热力图展示相关矩阵时也要注意标注好用的是哪种相关系数别让读者产生误解。第四个坑是忽略多重比较问题。一次性计算10个变量两两之间的相关系数就会得到45个p值其中可能有2到3个假阳性。如果要做严格的显著性判断可以考虑用Bonferroni校正也就是把显著性水平除以比较次数虽然保守但至少不会闹出拿随机噪声当显著相关的笑话。最后一个坑是过度解读。斯皮尔曼相关系数衡量的只是单调关联强度并不等于因果关系的存在。气温升高和冰淇淋销量上升的相关系数可能很高但真正的原因可能是“夏天来了”。在建模论文中对相关性结果的解读要克制结合业务背景分析可能的影响机制而不是急着写“强相关意味着强因果”这是评委眼里非常减分的一种表述。我把这些年来碰到的实际问题整理下来最想强调的还是那句老话先看数据再选方法。斯皮尔曼不是万能的但它能帮你在数据没那么“规整”的时候依然给出一个值得信任的关联性判断。做数学建模这几年我越来越觉得一个聪明的选手不是会背多少公式而是知道在什么条件下选什么工具并且能把这个选择的前因后果讲清楚。希望这篇文章能帮你少走点弯路比赛时多拿几个分。