3. 探索性数据分析(EDA)与可视化:用Pandas、Matplotlib、Seaborn摸清数据的脾气
说实话,很多初学者拿到数据就急着建模。我当年也犯过这个错——直接丢进回归模型,结果R²负得离谱。后来才明白,EDA不是走过场,是给数据做体检。你想想看,连数据长什么样都不知道,怎么指望模型能预测准?
这一节,我们就用Pandas做数据概览,用Matplotlib和Seaborn画趋势图、热力图,再聊聊数据分布特征。嗯,都是实战里每天要用的东西。
3.1 数据概览:Pandas三板斧
拿到数据后,我习惯先跑三个函数:head()、info()、describe()。别小看这三步,能帮你快速发现80%的问题。
import pandas as pd
import numpy as np
# 加载环境监测数据
df = pd.read_csv('air_quality.csv')
# 第一板斧:看前几行
print(df.head())
# 第二板斧:看数据类型和缺失值
print(df.info())
# 第三板斧:看统计摘要
print(df.describe())
info() 里的非空计数。如果某列缺失超过30%,我会考虑是否要删除或插补。曾经有一次,PM2.5数据缺失了40%,我直接用均值填充,结果模型偏差很大——后来改用时间序列插值才解决问题。
举个例子,假设数据长这样:
| 日期 | 温度(°C) | 湿度(%) | PM2.5(μg/m³) | 风速(m/s) |
|---|---|---|---|---|
| 2024-01-01 | 12.5 | 65.0 | 85.3 | 3.2 |
| 2024-01-02 | 14.1 | 58.0 | 72.1 | 4.5 |
| 2024-01-03 | 10.8 | 72.0 | NaN | 2.8 |
看到第三行PM2.5是NaN了吗?这就是info()能帮你快速定位的问题。
3.2 趋势图:时间序列的“心电图”
环境参数随时间变化,趋势图是最直观的观察方式。我个人喜欢用Matplotlib画折线图,配合Seaborn让图更漂亮。
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 绘制PM2.5随时间变化趋势
plt.figure(figsize=(12, 5))
plt.plot(df['日期'], df['PM2.5'], color='#2E86AB', linewidth=1.5, marker='o', markersize=3)
plt.title('PM2.5浓度日变化趋势', fontsize=14)
plt.xlabel('日期')
plt.ylabel('PM2.5 (μg/m³)')
plt.xticks(rotation=45)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
- 是否有明显的周期性?比如早晚高峰PM2.5升高
- 是否有异常突增或突降?可能是传感器故障或特殊事件
- 整体趋势是上升还是下降?这关系到模型是否需要考虑时间漂移
我在项目中遇到过这样的情况:某工厂的废气排放数据,趋势图显示每周一数值特别高。后来一查,原来是周一设备检修后重新启动,排放不稳定。这个发现直接帮我们优化了预测模型——加入了“星期几”这个特征。
3.3 相关性热力图:谁和谁“关系好”?
回归模型最怕特征之间高度相关(多重共线性)。热力图能一眼看出哪些变量“抱团”。
# 计算相关系数矩阵
corr_matrix = df[['温度', '湿度', 'PM2.5', '风速', '气压']].corr()
# 绘制热力图
plt.figure(figsize=(8, 6))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', fmt='.2f',
linewidths=0.5, square=True, cbar_kws={'shrink': 0.8})
plt.title('环境参数相关性热力图', fontsize=14)
plt.tight_layout()
plt.show()
热力图里,颜色越深表示相关性越强。红色是正相关,蓝色是负相关。举个例子:
| 温度 | 湿度 | PM2.5 | 风速 | |
|---|---|---|---|---|
| 温度 | 1.00 | -0.45 | 0.12 | -0.08 |
| 湿度 | -0.45 | 1.00 | 0.68 | -0.30 |
| PM2.5 | 0.12 | 0.68 | 1.00 | -0.55 |
| 风速 | -0.08 | -0.30 | -0.55 | 1.00 |
3.4 数据分布特征:直方图与箱线图
数据分布决定了你该用什么模型。正态分布?偏态分布?有没有异常值?这些都得看。
# 绘制直方图 + 核密度估计
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
sns.histplot(df['PM2.5'], bins=30, kde=True, color='#2E86AB')
plt.title('PM2.5分布直方图')
plt.subplot(1, 2, 2)
sns.boxplot(y=df['PM2.5'], color='#A23B72')
plt.title('PM2.5箱线图')
plt.tight_layout()
plt.show()
直方图能告诉你数据集中在哪个区间。比如PM2.5大多在50-100之间,但偶尔有超过200的——这些就是潜在异常值。
箱线图更直接:箱子中间的线是中位数,箱子范围是四分位距。超出“触须”的点就是异常值。我一般会这样处理:
- 轻度异常(超出1.5倍IQR):保留,但做对数变换
- 重度异常(超出3倍IQR):检查是否传感器故障,是则删除
- 业务异常:比如PM2.5超过500,可能是沙尘暴事件,单独标记
3.5 知识体系:EDA的核心逻辑
下面这张图是我自己总结的EDA流程,每次做项目都照着走一遍:
这个流程我用了好几年,每次做环境预测项目都按这个顺序来。说白了,EDA就是帮你在建模前把坑都踩一遍——总比模型跑出来才发现问题要好。
3.6 实战小贴士:EDA的“潜规则”
最后分享几个我踩过的坑:
- 别只看相关系数:相关系数为0不代表没关系,可能是非线性关系。画个散点图矩阵看看。
- 时间序列要检查平稳性:如果数据有趋势或季节性,直接用回归模型会出问题。我一般先用ADF检验。
- 异常值不一定是错误:比如PM2.5突然飙升到500,可能是附近发生了火灾。这种异常值反而是重要信号,不能简单删除。
- 可视化要“多角度”:同一个数据,折线图、直方图、箱线图看到的信息不一样。我习惯每个变量至少画两种图。
一句话总结: EDA做得好,建模事半功倍;EDA做得糙,模型跑得再快也是白搭。