第四章:探索性数据分析(EDA)——单变量分析、双变量分析与相关性热力图
好,咱们进入第四章。说实话,EDA 这一步,我见过太多人跳过了。他们拿到数据就急着建模,结果模型跑出来一塌糊涂,回头一看——数据本身就有问题。我个人习惯,拿到任何数据集,先花 60% 的时间做 EDA。磨刀不误砍柴工,这话在数据科学里尤其适用。
这一章,咱们聚焦三个核心任务:价格分布长什么样?尺寸和价格到底有没有关系?所有变量之间怎么互相影响的? 说白了,就是给你的数据做个全面体检。
4.1 单变量分析:价格分布的秘密
先看价格。艺术品价格,你想想看,它跟普通商品完全不一样。一幅画可能卖 1000 块,也可能卖 1 个亿。这种数据,分布通常极度右偏。
核心观察:艺术品价格服从对数正态分布,而非正态分布。
我建议你第一步,直接画个直方图看看原始价格分布。代码很简单:
import matplotlib.pyplot as plt
import seaborn as sns
# 原始价格分布
plt.figure(figsize=(10, 5))
sns.histplot(df['price'], bins=50, kde=True)
plt.title('艺术品价格分布(原始)')
plt.xlabel('价格(美元)')
plt.show()
你会看到什么?大概率是左边一堆矮柱子,右边一根长尾巴拖到天边。嗯,这就是典型的幂律分布。我在项目中遇到过这种情况,当时有个学生直接拿原始价格去跑线性回归,结果 R² 只有 0.02,气得直拍桌子。
正确的做法是什么?取对数:
import numpy as np
# 对数价格分布
df['log_price'] = np.log(df['price'])
plt.figure(figsize=(10, 5))
sns.histplot(df['log_price'], bins=50, kde=True)
plt.title('艺术品价格分布(对数变换后)')
plt.xlabel('对数价格')
plt.show()
取完对数,你会发现分布变得接近正态了。为什么?因为价格是乘法效应——一幅画贵 10 倍和贵 100 倍,在原始尺度上差距巨大,但在对数尺度上只是 1 和 2 的区别。这样建模才合理。
避坑指南:我曾经犯过一个错——取对数时没处理价格为 0 或负数的样本。结果 log(0) 直接报错,整个 pipeline 崩了。记得先检查:df['price'].min(),如果有 0,加一个小常数再取 log。
4.2 双变量分析:尺寸与价格的关系
接下来,咱们看看尺寸和价格的关系。直觉上,画越大应该越贵,对吧?但现实往往没那么简单。
先算个相关系数看看:
# 尺寸与价格的相关系数
corr = df['size'].corr(df['price'])
print(f'尺寸与价格的皮尔逊相关系数: {corr:.3f}')
我见过很多数据集,这个系数通常在 0.2 到 0.4 之间。不高,但显著。为什么会这样?因为尺寸只是影响价格的众多因素之一——艺术家名气、创作年代、流派、保存状况,哪个不比尺寸重要?
但别急着下结论。咱们画个散点图,加上回归线:
plt.figure(figsize=(10, 6))
sns.regplot(x='size', y='price', data=df, scatter_kws={'alpha':0.3})
plt.title('尺寸 vs 价格(原始尺度)')
plt.xlabel('尺寸(平方厘米)')
plt.ylabel('价格(美元)')
plt.show()
你可能会看到什么?一个扇形——尺寸越大,价格波动范围也越大。小尺寸的画,价格集中在低位;大尺寸的画,既有便宜的也有天价的。这叫异方差性,说白了就是方差随着自变量增大而增大。
注意:异方差性会破坏线性回归的假设。如果你直接拿原始数据建模,标准误会被低估,显著性检验就不可靠了。解决办法?还是取对数。试试 sns.regplot(x='log_size', y='log_price', ...),扇形通常会收窄。
我个人习惯,在双变量分析阶段,会同时看原始尺度和对数尺度的关系。有时候对数尺度下隐藏着更清晰的模式。比如,我记得有一次分析印象派画作,原始尺度下尺寸和价格几乎没相关性,但取对数后,居然出现了一个明显的正相关——原来是因为几幅超大尺寸的现代艺术拉低了整体趋势。
4.3 相关性热力图:全局视角
单变量看分布,双变量看关系,那多变量呢?用相关性热力图。它能让你一眼看出所有数值变量之间的线性关系。
# 选择数值列
num_cols = ['price', 'size', 'year', 'artist_age', 'auction_house_rank']
corr_matrix = df[num_cols].corr()
# 画热力图
plt.figure(figsize=(10, 8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm',
fmt='.2f', linewidths=0.5)
plt.title('数值变量相关性热力图')
plt.show()
这张图怎么看?我教你个口诀:颜色越深,关系越强;红色正相关,蓝色负相关。
通常你会看到:
- 价格与年份:弱正相关(0.1-0.3)。说明现代艺术品整体比古代贵,但波动很大。
- 价格与艺术家年龄:可能负相关(-0.1 到 -0.2)。年轻艺术家作品反而贵?不一定,可能是数据偏差——年轻艺术家作品少,物以稀为贵。
- 尺寸与拍卖行排名:正相关(0.3-0.5)。顶级拍卖行更倾向于拍卖大尺寸作品。
重要提醒:相关性不等于因果性。我看到热力图上价格和拍卖行排名有 0.4 的相关性,能说「因为进了苏富比所以价格高」吗?不能。也可能是「因为作品本身好,所以进了苏富比,同时价格也高」。混淆变量无处不在。
另外,我建议你做一个缺失值相关性检查。有时候,缺失值本身就是一个信号。比如,如果某幅画的尺寸缺失,可能意味着它被损坏过,或者来源不明——这些都会影响价格。
# 缺失值热力图
missing_corr = df.isnull().corr()
sns.heatmap(missing_corr, annot=True, cmap='viridis')
plt.title('缺失值相关性热力图')
plt.show()
嗯,这个技巧我是在一次 Kaggle 比赛中学到的。当时我发现「签名」字段的缺失与「价格」的缺失高度相关(0.85),后来一查,原来是那些没有签名的画作,很多是仿品,所以价格也没记录。这个发现直接帮我构建了一个「真伪概率」特征,模型 AUC 提升了 0.03。
4.4 本章知识体系
下面这张图,帮你把本章的核心逻辑串起来:
你看,EDA 不是随便画几张图就完事了。它是一个假设驱动的过程——先猜,再验证,再修正。单变量告诉你「数据长什么样」,双变量告诉你「两个变量怎么互动」,多变量告诉你「全局网络怎么连接」。三者缺一不可。
我的小习惯:每次做完 EDA,我会写一个「EDA 备忘录」,记录 3-5 个关键发现。比如:「价格右偏严重,需取对数」「尺寸与价格存在异方差性」「年份与拍卖行排名高度相关(0.6),建模时注意共线性」。这个备忘录,就是后续特征工程和模型选择的指南针。
好了,这一章就到这里。记住,EDA 做得越扎实,后面的建模就越轻松。别急着跑模型,先跟你的数据好好聊聊天。