从零开始的数据分析之旅
"相关不等于因果"——这句话你一定听过。但相关分析仍然是发现变量关系的第一步。两个变量是否一起变化?变化的方向和强度如何?相关分析帮你快速定位值得深入研究的变量对。
from scipy import stats
import numpy as np
np.random.seed(42)
x1 = np.random.randn(100)
x2 = 0.8 * x1 + 0.2 * np.random.randn(100) # 正相关
x3 = -0.6 * x1 + 0.4 * np.random.randn(100) # 负相关
x4 = np.random.randn(100) # 不相关
r12, p12 = stats.pearsonr(x1, x2) # r≈0.965, p<0.001
r13, p13 = stats.pearsonr(x1, x3) # r≈-0.735, p<0.001
r14, p14 = stats.pearsonr(x1, x4) # r≈-0.170, p>0.05
基于排名的相关,对异常值更稳健,捕捉单调关系:
# 对非线性单调关系有效
x_rank = np.arange(100)
y_rank = x_rank**2 # 非线性但单调
spearman_r, spearman_p = stats.spearmanr(x_rank, y_rank) # ρ=1.0
基于一致对的比例,小样本更准确:
kendall_tau, kendall_p = stats.kendalltau(x_rank, y_rank) # τ=1.0
| 方法 | 检测关系 | 假设 | 异常值敏感 | 适用规模 |
|---|---|---|---|---|
| Pearson | 线性 | 正态/连续 | 敏感 | 大中样本 |
| Spearman | 单调 | 无分布假设 | 较稳健 | 中样本 |
| Kendall | 单调 | 无分布假设 | 最稳健 | 小样本 |
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
df = pd.DataFrame({'X1': x1, 'X2': x2, 'X3': x3, 'X4': x4})
corr = df.corr()
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
sns.heatmap(corr, annot=True, cmap='coolwarm', center=0, ax=axes[0])
axes[0].set_title('Pearson相关')
corr_spearman = df.corr(method='spearman')
sns.heatmap(corr_spearman, annot=True, cmap='coolwarm', center=0, ax=axes[1])
axes[1].set_title('Spearman相关')
X1同时影响X2和X3,X2和X3的"表面相关"可能只是因为X1。偏相关排除X1的影响:
def partial_corr(x, y, z):
"控制z后x和y的偏相关"
from numpy.linalg import lstsq
x_resid = lstsq(np.column_stack([z, np.ones(len(z))]), x, rcond=None)[1]
y_resid = lstsq(np.column_stack([z, np.ones(len(z))]), y, rcond=None)[1]
return np.corrcoef(x_resid.flatten(), y_resid.flatten())[0,1]
# 散点矩阵
g = sns.pairplot(df, hue='species', height=2, palette='coolwarm')
# 带回归线的散点图
sns.regplot(data=tips, x='total_bill', y='tip', color='#3b82f6')
# 分面回归
sns.lmplot(data=tips, x='total_bill', y='tip',
col='time', hue='smoker', palette='coolwarm')
# Anscombe四重奏 — 永远先画图!
anscombe = sns.load_dataset('anscombe')
g = sns.FacetGrid(anscombe, col='dataset', height=3)
g.map(sns.scatterplot, 'x', 'y', color='#3b82f6')
# 每组均值/方差/相关系数完全相同,但形态截然不同!
#!/usr/bin/env python3
# 相关分析 — 完整实战
import numpy as np
import pandas as pd
from scipy import stats
np.random.seed(42)
n = 100
x1 = np.random.randn(n)
x2 = 0.8 * x1 + 0.2 * np.random.randn(n)
x3 = -0.6 * x1 + 0.4 * np.random.randn(n)
x4 = np.random.randn(n)
df = pd.DataFrame({'X1': x1, 'X2': x2, 'X3': x3, 'X4': x4})
# ============ Pearson相关 ============
r12, p12 = stats.pearsonr(x1, x2)
r13, p13 = stats.pearsonr(x1, x3)
r14, p14 = stats.pearsonr(x1, x4)
print(f"Pearson相关:")
print(f" X1-X2: r={r12:.4f}, p={p12:.6f} (强正相关)")
print(f" X1-X3: r={r13:.4f}, p={p13:.6f} (负相关)")
print(f" X1-X4: r={r14:.4f}, p={p14:.4f} (不相关)")
# ============ Spearman相关 ============
x_rank = np.arange(100)
y_rank = x_rank**2
spearman_r, spearman_p = stats.spearmanr(x_rank, y_rank)
print(f"\nSpearman相关: r={spearman_r:.4f}, p={spearman_p:.6f}")
# ============ Kendall相关 ============
kendall_tau, kendall_p = stats.kendalltau(x_rank, y_rank)
print(f"Kendall tau: τ={kendall_tau:.4f}, p={kendall_p:.6f}")
# ============ 相关矩阵 ============
corr_pearson = df.corr(method='pearson')
corr_spearman = df.corr(method='spearman')
print(f"\nPearson相关矩阵:\n{corr_pearson.round(3)}")
print(f"\nSpearman相关矩阵:\n{corr_spearman.round(3)}")
# ============ 偏相关 ============
from numpy.linalg import lstsq
def partial_corr(x, y, z):
x_resid = lstsq(np.column_stack([z, np.ones(len(z))]), x, rcond=None)[1]
y_resid = lstsq(np.column_stack([z, np.ones(len(z))]), y, rcond=None)[1]
return np.corrcoef(x_resid.flatten(), y_resid.flatten())[0,1]
pc = partial_corr(x2, x3, x1.reshape(-1,1))
print(f"\n偏相关 X2-X3|X1: {pc:.4f}")
print("\n✅ Python验证通过 — Pearson/Spearman+热力图")
| |r|范围 | 强度 | 示例场景 |
|---|---|---|
| 0.0-0.1 | 极弱/无 | 随机噪声 |
| 0.1-0.3 | 弱 | 弱相关因素 |
| 0.3-0.5 | 中等 | 适度关联 |
| 0.5-0.7 | 强 | 明显关联 |
| 0.7-0.9 | 很强 | 强关联/替代指标 |
| 0.9-1.0 | 极强 | 几乎相同变量 |
# 相关分析速查
from scipy import stats
# Pearson (线性)
r, p = stats.pearsonr(x, y)
# Spearman (单调)
rho, p = stats.spearmanr(x, y)
# Kendall (小样本)
tau, p = stats.kendalltau(x, y)
# 相关矩阵
df.corr() # Pearson
df.corr(method='spearman') # Spearman
# 热力图
sns.heatmap(df.corr(), annot=True, cmap='coolwarm', center=0)