4. 数据清洗与预处理:让数据“干净”起来
说实话,做机器学习项目这么多年,我踩过最大的坑往往不是模型选得不对,而是数据本身有问题。你想想看,垃圾数据喂进去,再牛的模型也白搭。今天咱们就聊聊数据清洗与预处理,这是整个空气质量预测模型的基石。
我个人习惯,拿到数据第一件事不是急着建模,而是先做一次“体检”。看看有没有缺失值、异常值,数据类型对不对,有没有重复记录。这一步做好了,后面能省80%的麻烦。
4.1 处理缺失值:删还是补?这是个问题
空气质量数据里,缺失值太常见了。传感器故障、网络中断、维护期间……各种原因都会导致数据缺失。我遇到过最夸张的一次,某个监测站连续三天没数据,后来发现是设备被鸟筑巢了。
处理缺失值,主要有两条路:删除和填充。
4.1.1 删除策略
什么时候该删?很简单:
- 缺失比例太高(比如超过50%),这个特征基本废了
- 缺失是随机发生的,且样本量足够大
- 你实在找不到合理的填充方法
代码实现也很直接:
import pandas as pd
# 删除含有缺失值的行
df_clean = df.dropna()
# 删除缺失比例超过50%的列
threshold = len(df) * 0.5
df_clean = df.dropna(thresh=threshold, axis=1)
4.1.2 填充策略
大多数情况下,我更倾向于填充。毕竟数据来之不易,能留就留。常用的填充方法有:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| 均值/中位数填充 | 数值型特征,数据分布较对称 | 简单快速,但会降低方差 |
| 众数填充 | 类别型特征 | 适合离散数据,但可能引入偏差 |
| 前向/后向填充 | 时间序列数据 | 保持时序连续性,但假设变化缓慢 |
| 插值法 | 数据有趋势或周期性 | 更精确,但计算量大 |
举个例子,空气质量数据是典型的时间序列。我一般先用前向填充,因为污染物浓度不会突变:
# 前向填充
df['PM2.5'] = df['PM2.5'].fillna(method='ffill')
# 如果还有缺失,再用插值
df['PM2.5'] = df['PM2.5'].interpolate(method='linear')
4.2 处理异常值:别让“离群点”带偏了模型
异常值,说白了就是那些“不合群”的数据点。比如PM2.5突然飙到1000,或者温度显示-50度。这些数据要么是传感器故障,要么是极端天气事件。
我常用的两种方法:IQR法则和3σ法则。
4.2.1 IQR法则
这个方法对异常值不敏感,适合数据分布偏态的情况。原理很简单:
- 计算第一四分位数(Q1)和第三四分位数(Q3)
- IQR = Q3 - Q1
- 正常范围:[Q1 - 1.5*IQR, Q3 + 1.5*IQR]
Q1 = df['PM2.5'].quantile(0.25)
Q3 = df['PM2.5'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df['is_outlier'] = (df['PM2.5'] < lower_bound) | (df['PM2.5'] > upper_bound)
4.2.2 3σ法则
这个方法假设数据服从正态分布。超出均值±3倍标准差的数据,被视为异常。适合数据分布比较对称的情况。
mean = df['PM2.5'].mean()
std = df['PM2.5'].std()
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
df['is_outlier'] = (df['PM2.5'] < lower_bound) | (df['PM2.5'] > upper_bound)
4.3 数据类型转换与标准化
这一步很多人会忽略,但恰恰是模型能否收敛的关键。
4.3.1 数据类型转换
我见过最坑的情况:日期列是字符串类型,模型直接报错。所以第一步,先把数据类型搞对:
# 转换日期格式
df['date'] = pd.to_datetime(df['date'])
# 转换类别型数据
df['wind_direction'] = df['wind_direction'].astype('category')
# 数值型数据确保是float
df['PM2.5'] = df['PM2.5'].astype(float)
4.3.2 标准化
为什么要标准化?你想想看,PM2.5的数值范围是0-500,而温度是-10到40度。如果不做处理,模型会天然认为PM2.5更重要,这显然不对。
常用的两种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | (x - μ) / σ | 数据近似正态分布 |
| Min-Max归一化 | (x - min) / (max - min) | 数据有明确边界 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['PM2.5', 'temperature', 'humidity']])
# Min-Max归一化
scaler = MinMaxScaler()
df_normalized = scaler.fit_transform(df[['PM2.5', 'temperature', 'humidity']])
4.4 重复数据处理
重复数据看起来无害,但会让模型“过度学习”某些模式。尤其是时间序列数据,重复记录会破坏时序关系。
处理起来很简单:
# 检查重复行
duplicates = df.duplicated().sum()
print(f"发现 {duplicates} 条重复数据")
# 删除重复行
df_clean = df.drop_duplicates()
# 基于特定列去重(比如同一时间点只能有一条记录)
df_clean = df.drop_duplicates(subset=['date', 'station_id'])
知识体系总览
下面这张图,把整个数据清洗与预处理的流程串起来了。你可以把它当作操作手册:
整个流程就像一条流水线。数据从原始状态进来,经过缺失值处理、异常值检测、类型转换、标准化、去重,最后变成干净可用的数据。每一步都不能跳过,否则模型质量就会打折扣。