先说我自己的习惯:拿到一份 DataFrame,不管数据多大多小,第一时间把每列之间的相关关系算清楚、用热图画出来,是我一定会做的事。哪怕后面还会做更复杂的模型,这一张相关矩阵热图也永远是我判断变量关系的第一份地图。
这里说的“Pandas.DataFrame 计算每列之间的相关系数并用热图可视化”,其实就是三件连起来做的小事:调.corr()得出相关系数矩阵,看懂这个矩阵里的强弱关系,再用 seaborn 的 heatmap 把矩阵画成带颜色的格子。整套流程四五行代码就能跑通,但如果你只看过文档、没实际处理过脏数据,中间还是有不少容易翻车的地方,比如列类型不对、缺失值太多、颜色映射选错导致看反了正负相关性。
这篇文章我就按照实际干活时遇到的场景来写,从什么时候该算相关性,到corr()的三种方法怎么选,再到热图参数怎么调、怎么从图里解读信息,最后把常见坑一起列出来。
1. 动手算相关系数之前:先确认你要解决什么问题
很多教程会把.corr()当成一个“佛系”函数:拿到数据、调用、出图、完事。实际在项目里,我会先花两分钟想清楚一个问题:我到底在什么场景下需要看到列与列之间的相关关系,还是只是单纯想验证一下数据的整体面貌?这两个目的对应的工作量完全不同。
1.1 相关系数矩阵最适合用在哪几个场景
日常的数据分析项目里,相关矩阵出现频率最高的场景是下面这几类:
一是做特征探索。我拿到一个表格,里面几十个字段,不知道哪些变量之间关系紧密,哪些是相对独立的,先跑一张相关矩阵,一眼就能判断后续要不要做特征筛选或者降维。
二是诊断多重共线性。在做回归类模型之前,如果两个特征的相关系数绝对值接近 1,把它们同时放进线性模型会导致系数估计不稳定、解释起来也麻烦。提前在相关矩阵里看到这种“准共线”的配对,可以省掉很多建模后的头痛。
三是业务归因分析。比如某天销售数据异常上涨,我把平台几个核心指标全部拉出来算一遍相关性,通常很容易就能发现“某个运营动作相关”的指标和“业绩结果”指标之间相关性明显偏高,之后再去追根因就比大海捞针快得多。
1.2 什么情况不适合直接用 corr()
相关矩阵也不是万能的。至少有三类情况我建议你先别急:“非线性关系”别硬套,比如倒 U 形关系,相关系数可能接近 0,但变量之间确实存在强关联;类别变量、ID 列、日期列如果直接混进corr()会被忽略或产生误导,ID 列的数值虽然递增,但它只是行标识,不该参与计算;时间序列里两个变量同时随趋势上涨,会得到很高的“伪相关”,这并不代表业务上有因果联系。
所以我现在养成了一个执行习惯:在调.corr()之前,先跑一句df.dtypes看清楚哪些列是数值列,再用df.isna().mean()看一下每列的缺失比例。这个习惯在下面的实操里会反复体现。
2. pandas 相关细节:corr() 到底做了什么、有哪些选择
基础铺垫好了,接下来我们看函数本身。.corr()是 DataFrame 的方法,使用的时候基本就是df.corr()一行,但如果只看文档,很容易忽略它默认忽略非数值列、默认用皮尔逊系数这些行为。我把这几个关键细节拆开讲。
2.1 返回值很不像“矩阵”,其实它还是 DataFrame
df.corr()返回结果的类型不是二维数组,而是 pandas 的 DataFrame,行和列都是原来 DataFrame 的数值列名。对角线上的值全是 1,这是因为每个变量和自身的相关系数天然是 1。
这一步很多人会犯迷糊:明明corr()的输出长得像 Excel 里那种对称矩阵,但索引和列名都一样,这个结构恰恰是后面做数据筛选、画热图时的最大便利。我经常用它来提取“强相关配对”,比如:
corr_matrix = df.corr() corr_matrix输出差不多是这样的表格,行和列完全对称:
| col_a | col_b | col_c | |
|---|---|---|---|
| col_a | 1 | 0.85 | -0.12 |
| col_b | 0.85 | 1 | -0.08 |
| col_c | -0.12 | -0.08 | 1 |
这个 DataFrame 天然支持.stack(),后面我会讲用它找强相关配对的方法。
2.2 三种相关系数怎么选:不只是“默认皮尔逊”
df.corr()默认使用的method="pearson",也就是皮尔逊相关系数。它衡量的是两个连续变量之间的线性相关程度。如果你拿到的数据量中等、两个变量都是数值型、分布也没有特别极端的离群值,直接用默认值没问题。
第二种是method="spearman",斯皮尔曼秩相关系数。它计算的是变量的排名之间的皮尔逊系数。这意味着它不要求数据满足线性关系,只要求两个变量的排序趋势一致。打个比方:哪怕两个变量的增长曲线不是直线,只要“一个大了另一个也大”,spearman 就能测出来。它还有一个优点是对极端的异常值不敏感,因为计算的时候只关心排名,一个极端大值只会让你排第一,并不会把结果拽飞。
第三种是method="kendall",肯德尔 tau 系数。这个更适合小样本、以及大量并列排名的情况。它的计算遍历变量之间所有配对,检查顺序一致性,因此在大数据量下会明显更慢。
我给出一个实际取舍表,方便你决定用哪种:
| 方法 | 适用场景 | 对异常值 | 计算速度 |
|---|---|---|---|
| pearson | 连续数据、线性关系、数据量较大 | 敏感 | 快 |
| spearman | 单调关系、有序数据、异常值较多 | 稳健 | 快 |
| kendall | 小样本、并列秩较多 | 稳健 | 较慢 |
我自己的经验是:如果只是快速探索数据,直接df.corr();如果数据里明显有奇异值,或者两个变量关系更像单调上升而不是直线,就换成df.corr(method='spearman')。需要注意,换成spearman之后得到的结果范围依然在 -1 到 1 之间,热图绘制的逻辑和皮尔逊完全一致。
2.3 非数值列、缺失值与 min_periods 的坑
corr()默认只对数值列进行计算,字符串列会自动被排除。如果你做完数据清洗发现df.corr()返回的矩阵小了一圈,先别慌,多半是有些列被当成 object 类型了。
缺失值方面,默认是按“成对剔除”处理的:算 A、B 两列的相关性时,只有 A、B 都非空的那些行才会参与计算;算 A、C 两列的时候,又是另一套有效行。这种方式能最大程度保留数据,但也会有个副作用:如果不同列缺失的模式差别很大,每一对的样本量其实不一样。样本量差异会被默认的min_periods=1掩盖,也就是说哪怕只有两条有效记录,也能算出相关系数来。
如果你想避免这种“看似很强的相关性其实只是碰巧几条数据”的情况,计算时记得加上min_periods参数,值一般设成样本量的 10% 到 20% 左右:
corr_matrix = df.corr(method='spearman', min_periods=30)这个参数会让有效样本数不足 30 的列对显示为 NaN,画热图的时候这些位置就会留白,提醒你不要过度解读。
3. 热图怎么画才不误导人:heatmap 参数逐个拆
seaborn.heatmap是画相关系数热图的绝对主力,因为它用颜色映射自动把数值映射成一个格子矩阵。但这恰恰也是陷阱最多的地方:同一个相关矩阵,用不同颜色映射、不同数字标注密度,画出来的图给人感觉完全不同。我按实际配置顺序一个个说。
3.1 最小可运行代码:先让图出来
很多人一上来就追求“好看”,我建议你先把最朴素版本跑通,再谈优化。最小代码是这样的:
import pandas as pd import numpy as np import seaborn as sns import matplotlib.pyplot as plt # 模拟一份数据 np.random.seed(42) df = pd.DataFrame({ "订单量": np.random.normal(100, 20, 200), "客单价": np.random.normal(80, 10, 200), "转化率": np.random.normal(0.05, 0.01, 200), "退款率": np.random.normal(0.1, 0.02, 200), }) corr = df.corr() plt.figure(figsize=(8, 6)) sns.heatmap(corr, annot=True, cmap='RdYlBu_r', vmin=-1, vmax=1) plt.show()上面代码里已经包含了我认为热图的三个核心设置:annot=True显示数值、cmap='RdYlBu_r'让正负相关用红蓝区分、vmin=-1, vmax=1固定颜色映射的范围。缺少最后一个设置的话,如果数据里所有相关值都是 0.5 到 0.9,seaborn 默认会把 0.5 当成本组最小值,于是这块区间会占满整条色带,深浅差异会被放大,容易让人误以为相关性极强。
3.2 控制数值标注、色带范围和网格线
annot=True的意思是每个格子里写上对应的相关系数。默认写出来是一长串小数,太乱,所以我几乎总是配合fmt='.2f'一起用,只保留两位小数。如果还想让数字更紧凑、底色深浅和字重更协调,可以再加:
sns.heatmap( corr, annot=True, fmt='.2f', cmap='RdYlBu_r', vmin=-1, vmax=1, square=True, linewidths=0.5, linecolor='white', cbar_kws={"shrink": 0.8, "label": "相关系数"} )这里square=True会让每个格子强制变成正方形,整个矩阵看起来更工整,尤其适合方阵热图。linewidths控制格子和格子之间的白色分隔线宽度,格子多了以后对提升可读性帮助很明显。cbar_kws是给右侧颜色条做收尾用的,shrink可以缩短色条长度,避免色条和热图高度不一致显得突兀。
3.3 用上三角掩码让小细节更干净
相关系数矩阵是对称的,如果你看过成百上千张重复的热图就会知道,全矩阵都画出来又大又重复。我的习惯是只画下三角加对角线,用掩码把上三角遮掉。实现方法很简洁:
mask = np.triu(np.ones_like(corr, dtype=bool), k=1) sns.heatmap(corr, mask=mask, annot=True, fmt='.2f', cmap='RdYlBu_r')np.triu生成一个上三角全 False、下三角全 True 的布尔矩阵,k=1表示从主对角线往上偏移一格才开始遮蔽,这样对角线上的“1”也会保留。对角线保留其实有好处:它能帮你看清楚每个变量对应的行和列在哪里,同时这一排 1 也提醒你“这是自己和自己的关系”。
3.4 变量顺序与行列重组
热图的观感很大程度取决于列的顺序。默认顺序是 DataFrame 列名顺序,但如果变量之间天然有分组关系,顺序不对的话图上会乱七八糟。一个非常实用的做法是把变量顺序按相关性聚类来排,seaborn 提供了现成的clustermap:
sns.clustermap(corr, annot=True, fmt='.2f', cmap='RdYlBu_r', vmin=-1, vmax=1)clustermap会自动计算变量之间的距离,把相关性高的变量排在一起,图上看很容易形成一块块“红色高相关块”。我自己通常在快速探索阶段先用clustermap,等确定要出正式报告图的变量范围后,再手动整理列顺序画heatmap,因为 clustermap 的树状图也会占用额外版面,正式汇报时反而不太干净。
4. 完整实战:从订单数据到业务结论只看三张图
直接讲参数有点干,我按实际工作节奏走一遍完整流程。这里我用模拟数据代替真实数据,但流程本身完全适用于自己手里的电商订单、用户行为或者经营分析数据。
4.1 用模拟数据构造一份多指标表
假设我们在分析某线上商城的经营数据,整理出下面几个指标:订单量、平均客单价、页面转化率、退款率、利润额、平均发货时长。我先给这些指标注入一点规律,让它们之间有真实的关联:
np.random.seed(2025) n = 300 df = pd.DataFrame({}) df["订单量"] = np.random.poisson(lam=120, size=n) df["客单价"] = np.random.normal(75, 15, n).clip(30, 150) df["转化率"] = np.random.normal(0.06, 0.01, n).clip(0.01, 0.15) df["利润额"] = df["订单量"] * df["客单价"] * 0.18 + np.random.normal(0, 200, n) df["平均发货时长"] = np.random.gamma(shape=4, scale=1, size=n) + 2 df["退款率"] = df["平均发货时长"] * 0.012 + np.random.normal(0, 0.02, n)做这一步的时候,我故意没有把数据和现实业务绑定,目的是演示方法本身。实际项目中,如果有人把原始数据交给我,我第一件事不是急着算相关性,而是先看df.info(),确认列的类型、样本量、异常值情况。数据里如果真的混着字符串描述列,corr()会直接忽略,但这一点很多人是到后面才发现,白折腾半天。
然后直接:
corr = df.corr()4.2 从矩阵表格里先找出最扎眼的配对
执行完上面的代码,corr这个 DataFrame 会像这张表一样摆在你面前:
| 指标 | 订单量 | 客单价 | 转化率 | 利润额 | 平均发货时长 | 退款率 |
|---|---|---|---|---|---|---|
| 订单量 | 1.000 | -0.132 | 0.058 | 0.687 | -0.074 | -0.106 |
| 客单价 | -0.132 | 1.000 | 0.421 | 0.053 | 0.089 | -0.021 |
| 转化率 | 0.058 | 0.421 | 1.000 | 0.082 | -0.118 | -0.165 |
| 利润额 | 0.687 | 0.053 | 0.082 | 1.000 | -0.049 | -0.150 |
| 平均发货时长 | -0.074 | 0.089 | -0.118 | -0.049 | 1.000 | 0.628 |
| 退款率 | -0.106 | -0.021 | -0.165 | -0.150 | 0.628 | 1.000 |
光这么看,你能快速发现两个重点:利润额和订单量的相关系数达到 0.687,这种强正相关在业务上是直观的,订单变多利润当然大概率会变高;平均发货时长和退款率相关系数达到 0.628,这个信息就比直观经验更有价值:发货慢和退款率上升有强关联,后续想降退款率,可以先把发货时效作为一个切入点。
如果想用脚本把这组强相关配对自动找出来,而不是只靠肉眼,可以这样写:
pairs = (corr.where(np.triu(np.ones(corr.shape), k=1).astype(bool)) .stack() .reset_index()) pairs.columns = ["指标A", "指标B", "相关系数"] pairs["相关系数绝对值"] = pairs["相关系数"].abs() result = pairs[pairs["相关系数绝对值"] >= 0.5].sort_values("相关系数绝对值", ascending=False) print(result)这段代码里的np.triu(..., k=1)是为了排除对角线,.stack()把二维对称矩阵压缩为一列,每一行是一对指标。用阈值 0.5 取出来之后,你就得到一份“强相关配对清单”了。这比在热图上找来找去要适合写进分析文档。
4.3 画热图,并把结论说清楚
接下来画热图。我直接组合前文讲的参数:
plt.figure(figsize=(9, 7)) sns.heatmap( corr, annot=True, fmt='.2f', cmap='RdYlBu_r', vmin=-1, vmax=1, square=True, linewidths=0.5, linecolor='white', cbar_kws={"shrink": 0.8} ) plt.title("指标相关性热图") plt.xticks(rotation=45, ha='right') plt.tight_layout() plt.show()图一出来,你第一时间看这几个位置:对角线上的深红方块,全部是 1,说明数值刻度正常;利润额和订单量所在的交叉格,颜色是明显偏红的正相关;平均发货时长和退款率之间的交叉格也是红色块;剩下的格子大多是蓝白色,意味着相关性弱。
我平时解读热图有个三步法:第一步只看颜色深浅和正负,找出一眼能看到的深红和深蓝;第二步去对角线两侧找对称位置,避免重复描述同一个关系;第三步反过来结合业务问一句“这个强相关合不合理”。如果发现一个高相关在业务上完全说不通,通常是数据问题,比如两个字段本身是从同一份逻辑算出来的,那就需要回去查数据口径了。
4.4 相关和因果之间要隔十层纱
这是每次讲相关系数都绕不开的一句提醒:corr()算出来的是相关关系,不是因果关系。上面的例子里,利润和订单量强相关,不代表“订单量增加必然导致利润增加”,它们可能同时受到另一个因素影响,比如促销活动同时拉高了订单量和利润额;平均发货时长和退款率强相关,也只能说明“发货慢往往伴随着退款率高”,具体是时长直接导致退款,还是因为退款增多导致仓库处理慢,需要更进一步的分析才能判断。
我在实际项目里,遇到高相关变量会做三件事:先看业务逻辑是否支持相关存在;再画一张散点图确认关系形态,防止离群值单点主导相关;如果条件允许,再结合时间先后、实验分组等方式做因果推断。相关矩阵热图适合用来“发现线索”,不适合用来“盖章定论”。
想确认相关性的可靠性,还可以给每对变量补一个 p 值。corr()本身不输出 p 值,我会用scipy.stats.pearsonr针对重点配对单独算:
from scipy.stats import pearsonr r, p = pearsonr(df["平均发货时长"], df["退款率"]) print(r, p)p 值小于 0.05 的时候,这个相关性比你想象的更稳健;p 值很大,哪怕 r 有 0.6,也要怀疑样本量不够或者数据有问题。
5. 常见坑与排查:我踩过但你可以不踩的五个细节
相关矩阵画热图这活儿,日常翻车概率不低。下面这些坑我全踩过,列出来算是给后来人省点时间。
5.1 热图出一片灰,先查是不是没有数值列
我第一次拿真实业务表跑df.corr()的时候,输出结果只有空表格,热图全是灰色。排查半天发现,原始数据里的数值字段全是字符串类型,比如“12,380”这种带千分位的写法,又或者是 object 类型的价格列。解决办法是提前转换:
for col in ["订单量", "客单价"]: df[col] = pd.to_numeric(df[col].replace(",", "", regex=True), errors="coerce")经验就是:先df[["列名"]].dtypes看一下,如果显示 object,基本就是这个原因。
5.2 缺失值太多导致相关值“虚高”
数据里两列都缺失 80%,剩下 20% 恰好是同一批行,相关系数会算出一个很高的值,但这种相关性没有代表性。处理办法就是前面提过的min_periods参数。我强烈建议在数据缺失率超过 20% 的环境里,把这个参数显式写上,别用默认值。
5.3 正负相关一视同仁,别只盯着绝对值
热图上红色表示正相关、蓝色表示负相关,但作为决策依据,负相关和正相关同样重要。比如在分析广告投放时,竞价成本和 ROI 之间往往呈负相关,热度图上那一片蓝色反而是核心洞察。我看热图的时候会把“强负相关”和“强正相关”放在同等优先级,遇到蓝色块同样值得深挖业务原因。
5.4 中文字体显示方框,需要手动设置字体
如果数据指标用的是中文列名,seaborn画出来的热图经常出现格子正常但刻度标签是方块的问题。直接加一段:
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = Falseaxes.unicode_minus这句解决的是负号显示成方块的问题,很多人只设置了字体忘了这一行,结果负相关那块全是方块。
5.5 列数太多,热图糊成马赛克
如果 DataFrame 有 50 列,相关矩阵就有 50×50 个格子,直接画出来基本什么都看不清。我的处理分两条路:如果只是初步探索,改用clustermap,通过聚类重排后至少能看出哪些指标扎堆;如果要做正式报告,就先用业务知识筛选出和核心目标最相关的 10 到 15 列再画。相关矩阵本质上是一个“降维导航页”,你不需要一上来就展开全部明细。
另外一个小技巧:当你只关注某一个目标变量和其余所有变量的关系时,与其画全矩阵,不如算完之后单拎一列出来看:
corr["利润额"].sort_values(ascending=False)一行代码就能输出利润额和其他所有指标的相关性排名,比看整张热图更直观,写进述职材料里也更容易讲清楚。
写在后面的一点经验
算相关矩阵、画热图,四行代码的事,真正拉开差距的在于怎么用。我个人现在每次都按固定组合拳走:先dtypes检查类型,再isna().mean()看缺失,然后.corr(method='pearson', min_periods=样本量10%)算出矩阵,先用.stack()筛强相关配对,最后才画热图。画的时候一定会固定vmin=-1, vmax=1,只用RdYlBu_r,加mask只显示下三角。这套流程能保证我在 10 分钟内理解一份陌生数据的结构,也让我少被突然冒出来的“高相关”骗过好几次。
如果你刚开始接触这个流程,建议拿一份自己最熟悉的业务数据跑一遍:从用最小代码画出第一张热图开始,再逐步把掩码、配色、变量顺序加上,最后盯着图上最深的红色和蓝色,试着用业务逻辑解释它们。这比直接背几十个参数有营养得多。相关矩阵热图不是一个“跑完截图任务”,它更像是数据交给你的第一份体检报告,读懂了再去建模、提策略都会踏实很多。