短障备侍在数据分析的江湖里我们经常会听到老板或业务方抛出这样的问题“现在的年轻人越晚睡买护肤品是不是越疯狂”“我们APP的各种优惠券真的能提升用户的留存率吗”“天气越热这只股票是不是跌得越惨”面对这些问题很多新人容易犯 “凭感觉” 的错误“我觉得应该有关系吧……”数据分析不相信“我觉得”只相信证据。 而寻找变量之间关系强弱的这个过程就叫做相关分析。今天就带大家把相关分析的工具箱翻个底朝天从基础到进阶一次性讲透1. 什么是相关分析简单来说相关分析就是判断两个或多个事物之间是否存在某种联系以及这种联系有多紧密。但请务必记住数据分析界的第一铁律相关≠因果。相关公鸡叫了天亮了。它俩有关系经常一起发生因果因为公鸡叫了所以天亮了。这就错了天亮是因为地球自转不是因为鸡叫我们要做的就是用数值相关系数来量化这种“一起发生”的程度。2. 数据相关的“三剑客”皮尔森相关系数斯皮尔曼相关系数和肯达尔相关系数这是最常见的三种相关系数它们处理的是数值型或者有等级顺序的数据。2.1. 皮尔森相关系数精确测量的标准皮尔森相关是最常用的相关性分析方法适用于符合以下条件的数据连续数据定距或定比尺度数据服从正态分布变量间呈线性关系这是最“挑剔”也是最常用的指标。它要求数据是连续数值定距/定比并且最好服从正态分布钟形曲线。它衡量的是线性关系是不是一条直线。比如身高和体重。一般来说人越高体重越重这是一个比较标准的线性关系。它的取值范围从 -1 到 1。接近 1 表示正相关同涨同跌接近 -1 表示负相关此消彼长0 表示没关系。代码示例如下import numpy as npimport pandas as pdfrom scipy import stats# 模拟数据运动时间(小时/周)与体重指数(BMI)np.random.seed(42)# 生成100个样本n_samples 100exercise_time np.random.normal(5, 2, n_samples) # 平均每周运动5小时# BMI与运动时间负相关运动越多BMI越低bmi 25 - 0.5 * exercise_time np.random.normal(0, 1.5, n_samples)# 创建DataFramedata pd.DataFrame({运动时间_小时每周: exercise_time, BMI: bmi})# 计算皮尔森相关系数pearson_corr, p_value stats.pearsonr(data[运动时间_小时每周], data[BMI])print(f皮尔森相关系数: {pearson_corr:.3f})print(fP值: {p_value:.5f})# 运行结果皮尔森相关系数: -0.614P值: 0.00000图形展示的效果如下2.2. 斯皮尔曼相关系数打破正态分布的限制如果数据不服从正态分布或者有极端值比如马云的财富混进了我们的收入数据中皮尔森相关系数就不准了。这时候用斯皮尔曼相关系数。它看重的是排名Rank而不是具体数值。比如语文成绩排名和数学成绩排名我们不关心具体考了多少分只关心你的位次。代码示例如下# 模拟数据社交媒体表现np.random.seed(42)# 生成非正态分布的数据followers np.random.exponential(5000, 50) # 指数分布likes 0.1 * followers**1.2 np.random.normal(0, 1000, 50) # 非线性关系# 创建DataFramesocial_data pd.DataFrame({粉丝数: followers, 平均点赞数: likes})# 计算斯皮尔曼相关系数spearman_corr, spearman_p stats.spearmanr(social_data[粉丝数], social_data[平均点赞数])print(f斯皮尔曼相关系数: {spearman_corr:.3f})print(fP值: {spearman_p:.5f})# 运行结果斯皮尔曼相关系数: 0.857P值: 0.00000图形化结果如下2.3. 肯达尔相关系数小样本和有序数据的首选肯达尔相关也适用于等级数据与斯皮尔曼相关不同它更关注“和谐对”与“不和谐对”。通常用于样本量较小或者数据有很多并列排名的情况。比如两位面试官给5个候选人打分。我们要看这两位面试官的审美标准是否一致。代码示例# 模拟数据电影评分与票房np.random.seed(42)# 生成有序数据电影评分和票房排名movie_data pd.DataFrame({电影名称: [f电影{i} for i in range(1, 21)],评分等级: np.random.choice([1, 2, 3, 4, 5], 20, p[0.1, 0.2, 0.3, 0.3, 0.1]), # 1-5星票房排名: np.arange(1, 21), # 票房排名})# 添加一些相关性评分越高票房排名越好数字越小for i in range(len(movie_data)):if movie_data.loc[i, 评分等级] 4:movie_data.loc[i, 票房排名] max(1, movie_data.loc[i, 票房排名] - np.random.randint(3, 8))elif movie_data.loc[i, 评分等级] 2:movie_data.loc[i, 票房排名] min(20, movie_data.loc[i, 票房排名] np.random.randint(3, 8))# 计算肯德尔相关系数kendall_corr, kendall_p stats.kendalltau(movie_data[评分等级], movie_data[票房排名])print(f肯德尔相关系数: {kendall_corr:.3f})print(fP值: {kendall_p:.5f})# 运行结果肯德尔相关系数: -0.503P值: 0.00460图形化结果如下3. 偏相关分析谁是“第三者”有时候两个变量看起来关系很好其实是因为有“第三者”在捣乱。偏相关分析允许我们在控制其他变量的情况下分析两个变量之间的 纯 相关性。比如一个生活中的案例“冰淇淋销量” 和 “溺水事故数量”。数据统计发现冰淇淋卖得越好的日子溺水的人越多相关系数很高。难道吃冰淇淋会导致溺水当然不是背后的控制变量第三者是气温。气温高 - 买冰淇淋多 游泳的人多 - 溺水概率大。想要正确分析必须剔除控制变量气温的影响后再看另外两个变量冰淇淋和溺水是否还相关。下面的示例中我们先排除收入影响后分析教育水平与消费水平的关系。# 使用pingouin库进行偏相关分析需要安装pip install pingouinimport pingouin as pg# 模拟数据教育水平、收入和消费水平np.random.seed(42)n 100# 教育水平1-55为最高education np.random.choice([1, 2, 3, 4, 5], n, p[0.1, 0.2, 0.3, 0.3, 0.1])# 收入与教育水平正相关income 30000 15000 * education np.random.normal(0, 5000, n)# 消费水平与收入和教育水平都相关consumption 1000 0.3 * income 200 * education np.random.normal(0, 500, n)# 创建DataFramesocioeconomic_data pd.DataFrame({教育水平: education, 收入_元每月: income, 消费水平: consumption})print( 简单相关分析 )simple_corr, simple_p stats.pearsonr(socioeconomic_data[教育水平], socioeconomic_data[消费水平])print(f教育水平与消费水平的简单相关系数: {simple_corr:.3f})print(\n 偏相关分析控制收入)# 使用pingouin进行偏相关分析partial_corr pg.partial_corr(datasocioeconomic_data, x教育水平, y消费水平, covar收入_元每月)print(partial_corr.round(3))# 运行结果 简单相关分析 教育水平与消费水平的简单相关系数: 0.965 偏相关分析控制收入n r CI95% p-valpearson 100 0.129 [-0.07, 0.32] 0.204图形化结果如下4. 距离相关分析多变量关系的度量距离相关分析可以衡量两组变量每个变量组包含多个指标之间的相关性是多变量分析的有力工具。比如压力与工作效率耶克斯-多德森定律的关系。压力太小人会懒散效率低压力太大人会崩溃效率低只有适度的压力效率最高。这是一个 U型非线性 关系。这时候用Pearson去算结果可能是0因为它找不到直线但其实它们关系很紧密。下面的示例比较两个城市的综合发展水平经济、环境、社会等多维度指标。from scipy.spatial.distance import pdist, squareform# 模拟数据两个城市的多维指标np.random.seed(42)# 城市A和城市B的6个发展指标经济、环境、教育、医疗、文化、创新indicators [经济, 环境, 教育, 医疗, 文化, 创新]city_a np.array([85, 78, 90, 88, 82, 80]) np.random.normal(0, 5, 6)city_b np.array([88, 75, 87, 92, 79, 85]) np.random.normal(0, 5, 6)# 创建多个城市的比较数据cities_data pd.DataFrame({城市A: city_a,城市B: city_b,城市C: np.array([70, 85, 75, 80, 90, 72]) np.random.normal(0, 5, 6),城市D: np.array([92, 70, 85, 87, 76, 91]) np.random.normal(0, 5, 6),城市E: np.array([78, 88, 80, 85, 87, 78]) np.random.normal(0, 5, 6),},indexindicators,)print(各城市发展指标数据)print(cities_data.round(2))# 计算距离相关性自定义简化版def distance_correlation(x, y):计算距离相关性# 计算距离矩阵def dist_matrix(v):v np.array(v)n len(v)a np.zeros((n, n))for i in range(n):for j in range(n):a[i, j] abs(v[i] - v[j])return aA dist_matrix(x)B dist_matrix(y)# 双中心化def double_centering(D):n len(D)row_means D.mean(axis1)col_means D.mean(axis0)grand_mean D.mean()C np.zeros((n, n))for i in range(n):for j in range(n):C[i, j] D[i, j] - row_means[i] - col_means[j] grand_meanreturn CA_centered double_centering(A)B_centered double_centering(B)# 计算距离协方差和距离方差dCov_XY np.sqrt((A_centered * B_centered).sum() / (len(x) ** 2))dVar_X np.sqrt((A_centered * A_centered).sum() / (len(x) ** 2))dVar_Y np.sqrt((B_centered * B_centered).sum() / (len(x) ** 2))# 计算距离相关性dCor dCov_XY / np.sqrt(dVar_X * dVar_Y)return dCor# 比较城市A和城市B的距离相关性city_a_scores cities_data[城市A].valuescity_b_scores cities_data[城市B].valuesdcor distance_correlation(city_a_scores, city_b_scores)print(f\n城市A与城市B的距离相关性: {dcor:.3f})# 运行结果各城市发展指标数据城市A 城市B 城市C 城市D 城市E经济 87.48 95.90 71.21 87.46 75.28环境 77.31 78.84 75.43 62.94 88.55教育 93.24 84.65 66.38 92.33 74.25医疗 95.62 94.71 77.19 85.87 86.88文化 80.83 76.68 84.94 76.34 84.00创新 78.83 82.67 73.57 83.88 76.54城市A与城市B的距离相关性: 0.764图形化的结果如下5. 相关性卡方检验分类变量的关联分析如果我们分析的数据不是数字而是类别定类数据/低测度数据呢当两个变量都是分类变量定类数据时我们可以使用卡方检验来分析它们之间是否存在关联。比如性别男/女 与 爱喝的饮料奶茶/咖啡 是否相关这里没有大小之分只有类别。我们可以使用卡方检验来判断两个分类变量是否独立。下面的示例我们尝试分析性别与购物偏好类别之间的关系。# 模拟数据性别与购物偏好np.random.seed(42)# 创建列联表data pd.DataFrame({性别: np.random.choice([男, 女], 200),购物偏好: np.random.choice([电子产品, 服装鞋包, 美妆护肤, 运动户外, 家居生活], 200),})# 根据性别调整偏好概率创建一些关联for i in range(len(data)):if data.loc[i, 性别] 男:# 男性更可能偏好电子产品和运动户外if np.random.random() 0.3:data.loc[i, 购物偏好] 电子产品elif np.random.random() 0.5:data.loc[i, 购物偏好] 运动户外else:# 女性更可能偏好美妆护肤和服装鞋包if np.random.random() 0.3:data.loc[i, 购物偏好] 美妆护肤elif np.random.random() 0.4:data.loc[i, 购物偏好] 服装鞋包# 创建列联表contingency_table pd.crosstab(data[性别], data[购物偏好])print(性别与购物偏好的列联表)print(contingency_table)# 执行卡方检验chi2, p, dof, expected stats.chi2_contingency(contingency_table)print(f\n卡方检验结果)print(f卡方值: {chi2:.3f})print(fP值: {p:.5f})print(f自由度: {dof})print(f\n期望频数表)print(pd.DataFrame(expected, indexcontingency_table.index, columnscontingency_table.columns).round(2))# 计算Cramers V衡量分类变量关联强度def cramers_v(contingency_table):计算Cramers V系数chi2 stats.chi2_contingency(contingency_table)[0]n contingency_table.sum().sum()min_dim min(contingency_table.shape) - 1v np.sqrt(chi2 / (n * min_dim))return vcramers_v_value cramers_v(contingency_table)print(f\nCramers V系数: {cramers_v_value:.3f})print(解读0.1-0.3弱相关0.3-0.5中等相关0.5强相关)# 运行结果性别与购物偏好的列联表购物偏好 家居生活 服装鞋包 电子产品 美妆护肤 运动户外性别女 2 33 15 40 10男 10 2 45 10 33卡方检验结果卡方值: 78.093P值: 0.00000自由度: 4期望频数表购物偏好 家居生活 服装鞋包 电子产品 美妆护肤 运动户外性别女 6.0 17.5 30.0 25.0 21.5男 6.0 17.5 30.0 25.0 21.5Cramers V系数: 0.625解读0.1-0.3弱相关0.3-0.5中等相关0.5强相关图形化的结果如下6. 总结数据分析师在面对变量关系时要根据数据的 “长相” 来选工具方法 适用数据类型 特点皮尔森相关 连续、正态分布、线性关系 最常用对异常值敏感斯皮尔曼相关 连续但不正态或有序数据 稳健适用于单调关系肯德尔相关 有序数据小样本 适合等级数据解释直观偏相关 需控制其他变量影响 揭示变量间的直接关系距离相关 多变量组间关系 衡量多维度综合关联卡方检验 分类变量 检验类别间关联性我们在分析数据相关性的时候不要急于得出数据之间是否相关的结论。先看看下面的注意事项是否有违背相关性不等于因果性即使两个变量高度相关也不能断定一个导致另一个警惕第三变量问题可能两个变量都受到第三个未测量变量的影响注意异常值的影响异常值可能夸大或掩盖真实的相关性检查线性假设皮尔森相关要求线性关系非线性关系需要其他方法样本大小的重要性小样本的相关性可能不稳定