ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

详解Python进行数据相关性分析的三种方式

详解Python进行数据相关性分析的三种方式

前言


"做相关性分析"在 Python 里通常指三个系数:皮尔逊(Pearson)、斯皮尔曼(Spearman)、肯德尔(Kendall)。它们回答的问题并不相同,选错了会得出方向完全相反的结论。更常见的错误是:算出一个 0.8,就宣称"两个变量关系很强"——但相关系数既不等于因果,也不衡量非线性关系。


本文讲清这三种系数的定义差异、各自的适用条件与对离群值的敏感度,并给出纯标准库手写 Pearson 与 Spearman 的实现。标准库里的statistics模块从 3.10 起提供correlation,从 3.12 起支持秩相关(Spearman);Kendall 则不在标准库中,需要第三方库。


本机没有解释器也没有任何第三方库,代码只能逐行人工推演,不声称运行过。


一、Pearson:衡量线性关系


皮尔逊相关系数 r 的定义是协方差除以两个标准差的乘积,取值范围在 -1 到 +1 之间。它衡量的是线性关系的强度与方向:r 接近 +1 表示散点大致落在一条上升直线上,接近 -1 表示下降直线,接近 0 表示没有线性关系。


必须强调"线性"这两个字。一个完美的抛物线关系 y = x²(x 取对称区间)的 Pearson r 会接近 0,但两个变量显然高度相关(只是非线性)。这就是"相关系数为 0 不代表没关系"的经典反例。


Pearson 的前提条件:



  • 两个变量大致服从联合正态分布,关系是线性的;

  • 没有极端离群值;

  • 方差不为 0(常数序列无法计算)。


离群值对 Pearson 的破坏力极大。因为计算里出现的是原始数值的偏离量,一个远离群体几倍标准差的数据点,可以单独把 r 从 0.9 拉到 0.3,或者反过来从 0 拉到 0.9。


二、Spearman:衡量单调关系


斯皮尔曼相关系数先把两个变量的原始值各自换成秩(rank),再对秩计算 Pearson。它衡量的是单调关系:只要一个变量增大时另一个变量总是增大(不要求按固定比例),Spearman 就会接近 +1。


这个"先取秩"的动作带来两个直接好处:


对离群值不敏感。一个极端大值只会得到"最大秩",无论它是 100 还是 1000000,秩都一样。所以 Spearman 天然稳健。


能捕捉非线性但单调的关系。比如 y = e^x,Pearson 会低估关系强度,Spearman 接近 1。


代价是:它对非单调的关系依然无能为力。y = x² 在双向上都不单调,Spearman 同样接近 0。


官方文档明确写了 Spearman 的适用场景:适合有序数据(ordinal data),或者不满足线性比例要求的连续数据。另外要注意,秩相同(并列)时要取平均秩,否则结果会被并列值处理方式影响。


三、Kendall:基于一致对


肯德尔相关系数(Kendall's tau)走的是第三条路。它考察所有"数据点对",统计其中一致对(concordant)和不一致对(discordant)的比例:如果 x 增大时 y 也增大,这一对一致;否则不一致。tau 就是(一致对数 − 不一致对数)除以总对数。


直觉上,Kendall 表达的是"随机抽两个样本,它们的 x 大小顺序与 y 大小顺序相同的概率"减去相反的情况。它同样是基于秩的,所以也稳健;与小样本或大量并列(tie)的场景相比,Spearman 与 Kendall 数值会不同,但方向一致。




系数度量的是对离群值前提取值范围



Pearson线性关系强度极敏感线性、近似正态、无极端值-1 到 +1

Spearman单调关系强度不敏感单调、可处理有序数据-1 到 +1

Kendall一致对占比不敏感单调、对并列有专门修正-1 到 +1



四、实战:纯标准库手写 Pearson 与 Spearman


下面两个函数只依赖math和statistics,可以逐行推演。


# 适用于 Python 3.8+
import math
import statistics


def pearson(x, y):
"""皮尔逊相关系数。要求 len(x) == len(y) 且长度大于 1。"""
if len(x) != len(y) or len(x) < 2:
raise ValueError("两个序列长度必须相同且不少于 2")
mx = statistics.mean(x)
my = statistics.mean(y)
num = sum((a - mx) * (b - my) for a, b in zip(x, y))
dx = math.sqrt(sum((a - mx) ** 2 for a in x))
dy = math.sqrt(sum((b - my) ** 2 for b in y))
if dx == 0 or dy == 0:
raise ValueError("存在常数序列,相关系数无定义")
return num / (dx * dy)


def ranks(values):
"""把数值换成秩;并列值取平均秩。"""
order = sorted(range(len(values)), key=lambda i: values[i])
result = [0.0] * len(values)
i = 0
while i < len(order):
j = i
while j + 1 < len(order) and values[order[j + 1]] == values[order[i]]:
j += 1
avg_rank = (i + j) / 2 + 1 # 秩从 1 开始
for k in range(i, j + 1):
result[order[k]] = avg_rank
i = j + 1
return result


def spearman(x, y):
"""斯皮尔曼相关系数 = 对秩求皮尔逊。"""
return pearson(ranks(x), ranks(y))


if __name__ == "__main__":
a = [1, 2, 3, 4, 5]
b = [2, 4, 6, 8, 10] # 完全线性单调
c = [1, 4, 9, 16, 25] # 单调但非线性
d = [5, 4, 3, 2, 1] # 完全反向

print("Pearson(a,b) =", round(pearson(a, b), 4))
print("Pearson(a,c) =", round(pearson(a, c), 4))
print("Spearman(a,c) =", round(spearman(a, c), 4))
print("Spearman(a,d) =", round(spearman(a, d), 4))

ranks里那段并列处理是关键:while j + 1 < len(order) and values[order[j + 1]] == values[order[i]]找出所有值相等的下标,然后给它们同一个"平均秩"。如果跳过这一步,并列值的先后顺序由排序的稳定性决定,结果就会出现微小抖动,同一份数据算两次可能不一样。


下面复用这条思路,演示标准库自带的接口,并对比三种系数在同一组数据上的差异。


# 适用于 Python 3.12+(statistics.correlation 的秩相关方法在 3.12 才加入)
from statistics import correlation

x = [1, 2, 3, 4, 5]
y = [1, 4, 9, 16, 25]

print("线性(Pearson):", round(correlation(x, y), 4))
print("秩(Spearman):", round(correlation(x, y, method="ranked"), 4))

注意这里的参数名是method="ranked",不是"spearman"。这个接口在Python 3.10引入(当时只有线性方法),3.12才加入"ranked"选项。所以如果要在 3.10 或 3.11 上算 Spearman,标准库没有现成接口,只能像上面那样手写,或者引入第三方库。


第三方的scipy.stats里有pearsonr、spearmanr、kendalltau,pandas的 DataFrame 也有相关矩阵方法;本机无法安装,具体函数名、参数与返回值以各库官方文档为准。


五、Anscombe 四组数据:为什么必须画图


统计学家 Anscombe 构造了四组数据,它们的 x 均值、y 均值、方差、Pearson 相关系数、回归直线几乎完全一致,但散点图形态截然不同:一组是标准线性、一组是明显抛物线、一组是带一个离群点的直线、一组是几乎全部点挤在一处、靠一个点撑起整条线。


这组例子说明三件事:



  1. 一堆统计量相同的数字,可能需要完全不同的模型。只看相关系数会得出"四组数据一样"的错误结论。

  2. 相关系数只对一件事负责——线性关系的强度,它不负责告诉你关系长什么样。

  3. 离群值能独自决定相关系数。第四组数据的相关系数由那一个点支配。


所以规范流程是:先画散点图,再算相关系数。图能告诉你关系是不是线性、有没有离群点、有没有分组结构。


常见坑点



  1. 把相关系数当因果证据。


❌ "两者相关系数 0.85,说明 A 导致了 B。" ✅ 相关只描述共变,因果需要额外的识别假设;也可能存在共同原因或第三变量。



  1. 用 Pearson 衡量非线性关系。


❌ 面对 U 形或抛物线关系,只算 Pearson 得到接近 0 就宣布"无关"。 ✅ 先画图判断形状;单调但非线性时改用 Spearman,必要时做变量变换后重算。



  1. 无视离群值对 Pearson 的影响。


❌ 直接对未清洗数据算 Pearson 并据此下结论。 ✅ 先看散点图识别离群点,或改用对秩的 Spearman,并说明选择理由。



  1. 记错标准库的参数名。


❌ 写correlation(x, y, method="spearman"),期待得到秩相关。 ✅ 正确的取值是method="ranked";而且在 3.12 之前这个选项根本不存在。



  1. 并列值不取平均秩。


❌ 手工实现秩相关时,直接按下标给出 1、2、3……,忽略取值相同的元素。 ✅ 取值相同的必须取平均秩,否则结果依赖排序实现细节,不可复现。



  1. 拿相关系数大小直接比较不同数据集。


❌ "A 组 r=0.6 比 B 组 r=0.5 关系更强,所以 A 组更适合建模。" ✅ 不同样本量、不同取值范围下系数的可比性有限,要结合散点图、样本量与置信区间一起判断。



  1. 对常数序列求相关系数。


❌ 某一列取值全相同仍强行计算,得到一个 NaN 或抛异常后不知所措。 ✅ 常数序列的标准差为 0,相关系数无定义;应在计算前显式检查并给出明确提示。



  1. 只报告一个数字,不报告样本量和方法。


❌ 报告"相关系数 0.7",不说是哪种系数、多少样本。 ✅ 写明用的是 Pearson 还是 Spearman、样本量多大、是否剔除过数据。


总结




方法关系类型离群值敏感度适用数据标准库支持



Pearson线性高近似正态、线性、无极端值correlation(x, y),3.10+

Spearman单调低有序数据、非线性单调、有离群值correlation(x, y, method="ranked"),3.12+

Kendall单调(一致对)低小样本、并列较多标准库无,需第三方



选系数只有一句话:关系是不是线性?数据里有没有极端值?两个问题答完,方法就定了。但无论选哪个,都请记住——先画图,再算数;数字是辅助,不是结论。所有第三方库的函数与参数,请以各自官方文档的当前版本为准。




RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进