相关≠因果!机器学习中皮尔逊相关检验的完整流程

哈喽,我是我不是小upper~

今儿和大家详细的聊聊,统计检验方法中最为重要的一个:皮尔逊相关系数检验~

最初,大家可能听过这句话:相关不等于因果

相关到底是什么?

怎么检验它?

我们在机器学习和数据分析里,最常用的起手式就是皮尔逊相关系数检验


01 皮尔逊相关系数到底是干嘛的?

在数据分析、机器学习特征工程、统计学检验中,皮尔逊相关系数(Pearson Correlation Coefficient) 是最常用、最基础的指标之一。

它本质上只干两件事:

  1. 判断两个数值型变量 之间是否存在线性关系
  2. 评估这种线性关系是真实存在,还是仅仅由随机波动导致的巧合

这里的线性关系可以简单理解为:

  • 变量 A 上升时,变量 B 也跟着上升 → 正相关
  • 变量 A 上升时,变量 B 反而下降 → 负相关
  • A 怎么变,B 都没有明显规律 → 无线性关系

皮尔逊相关系数通常记为 r ,取值范围严格落在 [-1, 1] 之间:

  • r 接近 1:强正线性相关
  • r 接近 -1:强负线性相关
  • r 接近 0:无线性相关(但不代表没有非线性关系)

除此之外,我们通常还会搭配 p 值(显著性水平) 一起看:

  • p 值很小(如 p < 0.05):观测到的相关性极不可能是随机巧合 → 相关性显著
  • p 值很大(如 p > 0.05):当前相关很可能只是运气导致 → 无显著线性相关

可以简单记:

  • r:衡量线性关系强弱与方向的 "温度计"
  • p 值:判断这个 "温度计读数" 是不是误差的校验器

02 一个通俗例子

用一个发糖果的场景,让你瞬间理解相关系数的含义。
规则:每个小朋友做对 X 道题(0~10 题),你按照 糖果数 Y = 做对题数 × 2 发糖。

示例:

小明做对 5 题 → 得到 10 颗糖
小红做对 8 题 → 得到 16 颗糖
小强做对 0 题 → 得到 0 颗糖

把 X(做对题数)和 Y(糖果数)画在坐标系里,几乎就是一条完美直线。

此时皮尔逊相关系数 r ≈ 1,代表极强正线性相关。

如果加入一点随机扰动,比如偶尔多发或少发 1~2 颗糖,数据会轻微抖动,r 可能从 1 降到 0.95 左右,但依然极强。此时做显著性检验:

如果真实世界中 X 和 Y 完全无关,纯靠随机能碰巧得到 0.95 这么高的相关吗?

概率极低,因此 p 值会非常小(如 p < 0.001) ,我们认为相关性显著。
再看反例:鞋带颜色 和 做题正确率,两者基本无关。此时 r 接近 0,p 值很大(如 0.6、0.7),说明没有证据表明二者存在线性关系。

03 核心点

1. 统计检验思想

皮尔逊相关系数的检验基于假设检验

  • 原假设 H₀:总体中两个变量不存在线性相关(总体相关系数 ρ = 0)
  • 备择假设 H₁:总体中两个变量存在线性相关(总体相关系数 ρ ≠ 0)

2. 皮尔逊相关系数公式

总体皮尔逊相关系数:

样本皮尔逊相关系数:

公式拆解:

  • 分子:X 与 Y 的协方差,衡量两个变量同向 / 反向变化的趋势
  • 分母:X 标准差 × Y 标准差,用于归一化,把结果约束在 [-1,1]

计算出 r 后,可构造 t 统计量进行显著性检验:

服从自由度 df = n - 2 的 t 分布,据此计算双侧 p 值。

3. 关键假设与注意事项

皮尔逊相关系数并非万能,它有严格适用条件:

  1. 只捕捉线性关系对非线性关系(U 型、抛物线、环形、周期波动)会失效,可能出现 r≈0 但实际明显相关。
  2. 要求成对观测值相互独立
  3. 理想情况下服从双变量正态分布
  4. 对异常值(离群点)极其敏感少数极端点就能大幅拉高 / 拉低 r 值,导致结果失真。
  5. 样本量较大时,对正态性的偏离会更稳健,但离群点的影响始终存在

04 什么时候用?什么时候别用?

适用场景

  • 快速探索两组数值变量之间的线性关联强度与方向
  • 机器学习中特征初筛:快速找出与标签相关性较强的特征
  • 回归分析前,判断自变量与因变量是否存在线性趋势
  • 简单的线性关联验证与显著性检验

不适用 / 需谨慎场景

  • 存在明显非线性关系(环形、抛物线、指数关系等)
  • 数据包含重度离群点,未做清洗直接计算
  • 变量是分类变量(性别、是否患病、等级等),此时应使用:
    • 点二列相关
    • 斯皮尔曼秩相关
    • 卡方检验等
  • 存在多重共线性,变量之间高度互相解释,需结合 VIF 方差膨胀因子进一步分析
  • 数据不独立、不满足近似正态分布(小样本尤其要注意)

05完整案例

我们先合成一组有强正相关、强负相关、弱相关、非线性相关、独立噪声的数据。

用PyTorch实现皮尔逊r与p值计算

数据集说明

  • 构造X1,令Y_pos≈0.8×X1+噪声(强正),Y_neg≈-0.7×X1+噪声(强负)

  • 构造X2≈0.6×X1+噪声(多变量里也会体现正相关)

  • 构造Y_nl=sin(X1)+噪声(非线性但皮尔逊不一定高)

  • 构造Noise随机噪声(与X1近似独立)

  • 再构造一个圆环结构(A=cosθ+噪声, B=sinθ+噪声),A和B之间非线性强相关但r≈0

python 复制代码
import torch
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

torch.manual_seed(42)

n = 2000

# 1) 线性相关与噪声
X1 = torch.randn(n)
X2 = 0.6 * X1 + 0.8 * torch.randn(n)  # 与X1中等正相关
Y_pos = 0.8 * X1 + 0.5 * torch.randn(n)  # 强正相关
Y_neg = -0.7 * X1 + 0.5 * torch.randn(n) # 强负相关
Y_nl = torch.sin(X1) + 0.3 * torch.randn(n)  # 非线性关系(sin)
Noise = torch.randn(n)  # 近独立噪声

# 2) 圆环结构(相关系数r~0但关系显著非线性)
theta = 2 * torch.pi * torch.rand(n)
A = torch.cos(theta) + 0.15 * torch.randn(n)
B = torch.sin(theta) + 0.15 * torch.randn(n)

# 3) 构造标签
group = torch.where(X1 + 0.5*X2 > 0, 1, 0)  # 0/1两类
group = group.numpy()

# 拼接到DataFrame
df = pd.DataFrame({
    "X1": X1.numpy(),
    "X2": X2.numpy(),
    "Y_pos": Y_pos.numpy(),
    "Y_neg": Y_neg.numpy(),
    "Y_nl": Y_nl.numpy(),
    "Noise": Noise.numpy(),
    "A": A.numpy(),
    "B": B.numpy(),
    "group": group
})

实现皮尔逊相关与显著性检验

  • 写一个函数pearsonr_torch(x, y)计算r与p

  • 写一个函数corr_p_matrix_torch(M)对矩阵每对列做检验,得到r矩阵和p矩阵

  • 使用t分布求p值:

python 复制代码
import torch
import numpy as np
import pandas as pd
from scipy.stats import t  # 这里加了 scipy 的 t 分布

def pearsonr_torch(x: torch.Tensor, y: torch.Tensor):
    """
    输入: x,y (1-D张量,长度n)
    输出: r, p (标量tensor)
    """
    assert x.ndim == 1 and y.ndim == 1 and x.shape[0] == y.shape[0]
    n = x.shape[0]
    x = x - x.mean()
    y = y - y.mean()
    r = (x @ y) / (torch.sqrt((x @ x)) * torch.sqrt((y @ y)))
    # 数值稳定:截断到[-1+eps, 1-eps]
    eps = 1e-12
    r = torch.clamp(r, -1+eps, 1-eps)
    # t统计量
    df_val = n - 2
    t_val = r * torch.sqrt(torch.tensor(df_val, dtype=torch.float64) / (1 - r**2))
    t_val = t_val.to(torch.float64)

    # ---------------- 修复:用 scipy 计算 t 分布 p 值 ----------------
    t_np = np.abs(t_val.cpu().numpy())
    p_np = 2 * (1 - t.cdf(t_np, df=df_val))
    p = torch.tensor(p_np, dtype=torch.float32, device=x.device)
    # ----------------------------------------------------------------

    return r.to(torch.float32), p

def corr_p_matrix_torch(M: torch.Tensor):
    """
    输入: M (n x d), 每列一个变量
    输出: R (d x d), P (d x d) 对称矩阵
    """
    assert M.ndim == 2
    n, d = M.shape
    R = torch.zeros((d, d), dtype=torch.float32)
    P = torch.zeros((d, d), dtype=torch.float32)
    for i in range(d):
        for j in range(i, d):
            r, p = pearsonr_torch(M[:, i], M[:, j])
            R[i, j] = R[j, i] = r
            P[i, j] = P[j, i] = p
    return R, P

# 只对数值型列做矩阵计算
num_cols = ["X1", "X2", "Y_pos", "Y_neg", "Y_nl", "Noise", "A", "B"]
M = torch.tensor(df[num_cols].values, dtype=torch.float32)
R, P = corr_p_matrix_torch(M)

# 转成DataFrame便于可视化
R_df = pd.DataFrame(R.numpy(), index=num_cols, columns=num_cols)
P_df = pd.DataFrame(P.numpy(), index=num_cols, columns=num_cols)

快速检查:几对关键变量的r与p

python 复制代码
pairs = [("X1","Y_pos"), ("X1","Y_neg"), ("X1","Y_nl"), ("X1","Noise"), ("A","B")]
for a,b in pairs:
    r, p = pearsonr_torch(torch.tensor(df[a].values), torch.tensor(df[b].values))
    print(f"{a} vs {b}: r={r.item():.3f}, p={p.item():.3g}")

会看到:

  • X1 vs Y_pos: r较高(~0.85左右),p极小

  • X1 vs Y_neg: r较低(~-0.8左右),p极小

  • X1 vs Y_nl: r接近0或较小,p可能不显著(尽管X1和Y_nl有明显的sin非线性关系)

  • X1 vs Noise: r≈0,p不显著

  • A vs B: r≈0,p不显著(但实际上是圆环结构,非线性强)

这正说明了皮尔逊相关只盯线性

可视化分析

咱们一眼看出变量之间线性相关的强弱与方向(红=正相关,蓝=负相关)

可用于特征之间的冗余分析、初步筛选

  • X1与Y_pos强正(颜色偏红),X1与Y_neg强负

  • X1与X2中度正相关

  • Y_nl与X1颜色不显著------尽管它们有非线性关系

  • A与B的r也不突出(几乎0),但别被它骗了

成对散点矩阵:

  • 检查每对变量之间的形状关系(线性、曲线、团簇、离群点)

  • 按group着色呈现类别差异,揭示潜在混杂

python 复制代码
use_cols = ["X1", "X2", "Y_pos", "Y_neg", "Y_nl"]
pair_df = df[use_cols + ["group"]].copy()
pair_df["group"] = pair_df["group"].astype("category")

# 强制轻量绘图
g = sns.pairplot(
    pair_df, 
    vars=use_cols, 
    hue="group",
    palette="tab10", 
    corner=True,
    kind="scatter",
    diag_kind="hist",  
    plot_kws=dict(s=8, alpha=0.4)
)
plt.show()
  • X1-Y_pos近似斜直线(正相关),X1-Y_neg近似斜直线但反方向(负相关)

  • X1-Y_nl出现S型或波浪形(非线性)

  • 可看到不同群组的分布差异,这提醒我们分组变量可能影响相关解读,如有混杂需控制。

显著性热力图(-log10(p)):

  • 和相关热力图互补:这张图专门看多显著

  • 可快速识别统计上置信度高的关联对

python 复制代码
neglogP_df = -np.log10(P_df + 1e-300)  # 数值稳定
plt.figure(figsize=(9, 7))
mask = np.triu(np.ones_like(neglogP_df, dtype=bool))
sns.heatmap(neglogP_df, mask=mask, cmap="magma", annot=True, fmt=".1f",
            linewidths=.5, cbar_kws={"shrink": .8})
plt.title("-log10(p) Significance Map (magma)")
plt.tight_layout()
plt.show()
  • X1-Y_pos、X1-Y_neg对应位置显著性非常强

  • X1-Y_nl、A-B等位置不亮,说明线性相关不显著(尽管它们存在非线性关系)

  • 可以用这张图来配合阈值(比如p<0.05,相当于-log10(p)>1.3)做显著对筛选

火山图:

  • 横轴看相关强度(取|r|),纵轴看显著性(-log10(p))

  • 右上角=又强又显著(重点),左下角=又弱又不显著(忽略)

python 复制代码
pairs = []
for i, a in enumerate(num_cols):
    for j, b in enumerate(num_cols):
        if j <= i: 
            continue
        pairs.append((a, b, abs(R_df.loc[a, b]), -np.log10(P_df.loc[a, b] + 1e-300)))

volcano = pd.DataFrame(pairs, columns=["VarA","VarB","abs_r","neglog10p"])

plt.figure(figsize=(8,6))
sc = plt.scatter(volcano["abs_r"], volcano["neglog10p"], 
                 c=volcano["neglog10p"], s=80, cmap="rainbow", alpha=0.9, edgecolor="k")
plt.colorbar(sc, label="-log10(p)")
plt.axvline(0.3, color="deepskyblue", ls="--", lw=1.2)  # 经验相关强度阈值
plt.axhline(1.3, color="crimson", ls="--", lw=1.2)      # p=0.05阈值的-log10
for _, row in volcano.sort_values("neglog10p", ascending=False).head(5).iterrows():
    plt.text(row["abs_r"]+0.01, row["neglog10p"]+0.1, f'{row["VarA"]}-{row["VarB"]}',
             fontsize=9, color="black")
plt.xlabel("|r| (Correlation Strength)")
plt.ylabel("-log10(p) (Significance)")
plt.title("Volcano-style Plot of Pairwise Correlations")
plt.tight_layout()
plt.show()
  • 右上角的点(如X1-Y_pos、X1-Y_neg)最值得关注:相关强且显著

  • 左下角(如X1-Noise)可忽略

  • 有些点|r|不高但也许p还不错,说明样本量较大时即便弱相关也可能显著,但实际意义未必大

线性 vs 非线性对比散点:

  • 强调皮尔逊相关只会抓线性

  • 对比X1-Y_pos(线性)和A-B(圆环结构、非线性强)

python 复制代码
fig, axes = plt.subplots(1, 2, figsize=(12,5))

# 线性:X1 vs Y_pos
axes[0].scatter(df["X1"], df["Y_pos"], c=df["X2"], cmap="viridis", s=30, alpha=0.9, edgecolor="white", linewidth=0.4)
axes[0].set_title("Linear: X1 vs Y_pos (colored by X2)")
axes[0].set_xlabel("X1")
axes[0].set_ylabel("Y_pos")

# 非线性:A vs B(圆环)
axes[1].scatter(df["A"], df["B"], c=df["Y_nl"], cmap="plasma", s=30, alpha=0.9, edgecolor="white", linewidth=0.4)
axes[1].set_title("Nonlinear: A vs B (Ring, colored by Y_nl)")
axes[1].set_xlabel("A")
axes[1].set_ylabel("B")

plt.suptitle("Linear vs Nonlinear Relationships (Bright Colors)")
plt.tight_layout()
plt.show()
  • 左图:X1与Y_pos像一条斜直线,皮尔逊r高

  • 右图:A与B呈环状,r≈0,但结构非常清晰,告诉你相关系数抓不到它们的非线性关系

总结

皮尔逊相关系数r用来衡量两个数值变量的线性 强弱与方向;检验p值用来判断这种强弱是不是大概率不是巧合

用法快、准,但只盯线性;非线性、离群点、分布偏态都可能让它失灵误判

我们在实战中,最好图+数双保险:用热力图、pairplot、散点图去感受形状;用r和p去定量判断。

相关推荐
云烟成雨TD2 小时前
Spring AI 1.x 系列【28】基于内存和 MySQL 的多轮对话实现案例
java·人工智能·spring
耿雨飞2 小时前
DeerFlow 系列教程 第五篇 | 配置与 Docker 部署全指南:从香港首建到内陆迁移
人工智能·deer-flow·llm应用开发平台
pwn蒸鱼2 小时前
leetcode:21. 合并两个有序链表
算法·leetcode·链表
深蓝轨迹2 小时前
#Python零基础机器学习入门教程
人工智能·python·机器学习
EMQX2 小时前
S3 正在吞噬一切:AI 时代的基础软件架构革命
人工智能·物联网·mqtt·flowmq
洛水水2 小时前
【力扣100题】15.删除链表的倒数第 N 个结点
算法·leetcode·链表
QC777LX2 小时前
传统法务工作重复度高,AI法律顾问正在改变格局
人工智能
枫叶林FYL2 小时前
【自然语言处理 NLP】7.2.2.3 隐私泄露评估(Privacy Leakage via Memorization)
人工智能·深度学习·机器学习
jarvisuni2 小时前
Claude官网克隆之Opus4.6
人工智能·ai编程