用Python实战斯皮尔曼相关系数:从手动推导到Pandas/Scipy一键调用
在数据分析领域,理解变量间的关系往往比单纯计算数字更重要。想象你手头有一组学生的数学和物理成绩,想知道这两门学科的表现是否存在某种关联——是数学好的学生物理也普遍优秀,还是两者毫无规律可循?这时候,斯皮尔曼相关系数就像一把精准的尺子,能测量出这种关系的强度和方向。
与常见的皮尔逊相关系数不同,斯皮尔曼系数不要求数据呈正态分布,也不受异常值的过度影响。它通过比较数据的排名而非原始值,揭示的是单调关系(一个变量增加时,另一个变量是否倾向于同向或反向变化)。这种特性使其在真实世界的数据分析中尤为实用,因为完美符合统计假设的理想数据集实在太罕见了。
1. 手动计算:深入理解算法本质
要真正掌握一个统计概念,没有什么比亲手计算一次更有效。让我们从一个简单的数据集开始:
# 示例数据:10名学生的数学和物理成绩(满分100) math_scores = [78, 92, 65, 88, 56, 72, 85, 90, 62, 80] physics_scores = [82, 95, 60, 86, 50, 75, 88, 92, 58, 84]1.1 数据转换为秩次
斯皮尔曼相关性的核心在于秩次(rank)转换。我们将原始分数转换为它们在各自科目中的排名:
import numpy as np def get_ranks(scores): # 使用scipy的rankdata处理相同值的情况 from scipy.stats import rankdata return rankdata(scores) math_ranks = get_ranks(math_scores) physics_ranks = get_ranks(physics_scores) print("数学成绩排名:", math_ranks) print("物理成绩排名:", physics_ranks)注意:当存在相同分数时(即出现"结"tie),通常采用平均秩次。例如两个并列第三的分数会都获得3.5的秩次。
1.2 计算秩次差异
接下来,我们计算每对学生两科排名的差值(d)及其平方:
| 学生 | 数学排名 (Rx) | 物理排名 (Ry) | d = Rx - Ry | d² |
|---|---|---|---|---|
| 1 | 4 | 4 | 0 | 0 |
| 2 | 9 | 10 | -1 | 1 |
| ... | ... | ... | ... | ... |
d_squared = (math_ranks - physics_ranks)**21.3 应用斯皮尔曼公式
对于没有相同秩次的数据,可以使用简化公式:
$$ \rho = 1 - \frac{6 \sum d_i^2}{n(n^2 - 1)} $$
其中n是样本量。我们的Python实现:
n = len(math_scores) spearman_rho = 1 - (6 * np.sum(d_squared)) / (n * (n**2 - 1)) print(f"手动计算的斯皮尔曼系数: {spearman_rho:.3f}")这个结果(约0.95)表明数学和物理成绩排名之间存在极强的正相关——数学排名高的学生,物理排名也倾向于较高。
2. 处理相同秩次(Tie)的情况
现实数据中经常出现相同值,这时需要采用更通用的皮尔逊相关系数公式计算秩次间的相关性:
from scipy.stats import pearsonr # 使用pearsonr计算秩次的相关性 rho, p_value = pearsonr(math_ranks, physics_ranks) print(f"考虑相同秩次的斯皮尔曼系数: {rho:.3f}")重要提示:当数据中存在大量相同秩次时,简化公式会高估相关系数。使用皮尔逊公式计算秩次是更可靠的方法。
3. 使用Scipy进行高效计算
虽然手动计算有助于理解,但在实际工作中我们更倾向于使用优化过的库函数。Scipy的spearmanr函数不仅能快速计算相关系数,还提供统计显著性检验:
from scipy.stats import spearmanr rho, p_value = spearmanr(math_scores, physics_scores) print(f"Scipy计算结果 - 相关系数: {rho:.3f}, p值: {p_value:.4f}")3.1 解读p值
p值(此处应远小于0.05)告诉我们,如果数学和物理成绩实际上毫无关联,我们观察到如此强相关性的概率有多大。低p值支持"两科成绩确实存在关联"的结论。
常见误解警示:p值大小并不直接反映相关性强弱,只说明相关性是否"显著"。强相关性可能因样本量小而p值不显著,反之亦然。
4. Pandas批量计算多变量相关性
当需要分析多个变量间的相关性时,Pandas提供了极其便捷的接口。假设我们有一个包含多科成绩的DataFrame:
import pandas as pd data = { '数学': math_scores, '物理': physics_scores, '化学': [75, 88, 62, 85, 52, 70, 82, 87, 60, 78], '生物': [80, 90, 58, 82, 55, 72, 85, 88, 63, 75] } df = pd.DataFrame(data) # 计算斯皮尔曼相关矩阵 corr_matrix = df.corr(method='spearman') print(corr_matrix)这将输出一个漂亮的对称矩阵,展示所有学科两两之间的相关性:
数学 物理 化学 生物 数学 1.000 0.945 0.927 0.891 物理 0.945 1.000 0.964 0.927 化学 0.927 0.964 1.000 0.945 生物 0.891 0.927 0.945 1.0004.1 可视化相关矩阵
为了更直观地理解,我们可以用热图可视化:
import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize=(8, 6)) sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', vmin=-1, vmax=1) plt.title("学科成绩斯皮尔曼相关矩阵") plt.show()5. 实际应用中的注意事项
5.1 样本量要求
虽然斯皮尔曼相关对小样本也能计算,但解释结果时需要谨慎:
- n < 10时,任何相关性结论都应视为初步探索
- n > 30时,结果通常更稳定可靠
- 当p值接近显著性阈值(如0.05)时,应考虑收集更多数据
5.2 非线性单调关系
斯皮尔曼能检测到皮尔逊相关可能遗漏的非线性单调关系。例如:
x = np.arange(0, 10, 0.1) y = x**2 + np.random.normal(0, 1, len(x)) pearson_r = pearsonr(x, y)[0] spearman_r = spearmanr(x, y)[0] print(f"皮尔逊系数: {pearson_r:.3f}, 斯皮尔曼系数: {spearman_r:.3f}")虽然y与x是二次关系而非线性,斯皮尔曼仍能检测到完美的单调关系(系数≈1),而皮尔逊相关则会低估这种关联。
5.3 异常值鲁棒性
对比皮尔逊相关,斯皮尔曼对异常值更稳健:
x_clean = np.random.normal(0, 1, 100) y_clean = x_clean + np.random.normal(0, 0.5, 100) # 添加一个极端异常值 x_outlier = np.append(x_clean, 10) y_outlier = np.append(y_clean, -10) print("干净数据:") print(f"皮尔逊: {pearsonr(x_clean, y_clean)[0]:.3f}") print(f"斯皮尔曼: {spearmanr(x_clean, y_clean)[0]:.3f}") print("\n含异常值数据:") print(f"皮尔逊: {pearsonr(x_outlier, y_outlier)[0]:.3f}") print(f"斯皮尔曼: {spearmanr(x_outlier, y_outlier)[0]:.3f}")可以看到异常值对皮尔逊相关的影响远大于斯皮尔曼。
6. 进阶应用:时间序列分析
斯皮尔曼相关在分析时间序列的单调趋势时特别有用。例如分析某股票价格与交易量之间的关系:
# 示例:股票价格与交易量的30天滚动斯皮尔曼相关 def rolling_spearman(price, volume, window=30): return price.rolling(window).corr(volume, method='spearman') # 假设df_stock包含'price'和'volume'列 df_stock['rolling_corr'] = rolling_spearman(df_stock['price'], df_stock['volume'])这种分析可以揭示市场行为的变化模式,比如价格上升初期可能伴随成交量放大(正相关),而价格高位时可能转为负相关。