数据预处理基础:缺失值处理、异常值检测、数据标准化与归一化
数据预处理,说白了就是给原始数据「洗澡」。
我做了这么多年环境数据分析,最深的体会就是:数据质量决定了分析的天花板。你模型再牛,算法再花哨,喂进去一堆脏数据,出来的结果也是垃圾。
这一章,咱们就聊聊数据预处理的三个核心环节:缺失值、异常值、还有标准化归一化。嗯,都是基本功,但坑也不少。
核心观点:预处理不是可选项,是必选项。跳过这一步,后面全是白费功夫。
一、缺失值处理:别让「空」毁了你的分析
环境数据里,缺失值太常见了。传感器故障、传输丢包、人为漏记……原因五花八门。
我个人习惯,拿到数据第一件事就是跑个 df.isnull().sum(),看看哪些列在「偷懒」。
1.1 缺失值类型
先搞清楚缺失的「性质」,再决定怎么处理:
- 完全随机缺失(MCAR):缺失跟数据本身没关系,比如仪器偶尔抽风。这种情况最「安全」,直接删或简单填充都行。
- 随机缺失(MAR):缺失跟其他变量有关。比如PM2.5监测仪在高温天更容易坏,那缺失就跟温度有关了。
- 非随机缺失(MNAR):缺失跟它自己有关。比如浓度太高把传感器烧了,那高值数据就丢了——这最麻烦。
我的经验:实际项目中,大部分缺失属于MAR。我曾经处理过一批水质监测数据,发现溶解氧的缺失跟水温高度相关——水温超过30°C时传感器就罢工。这种情况,用温度做辅助变量来插值,效果比简单填充好得多。
1.2 常用处理方法
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 直接删除 | 缺失比例 < 5%,且随机 | 简单粗暴 | 样本量减少,可能引入偏差 |
| 均值/中位数填充 | 数值型,缺失比例不高 | 计算快,不影响分布中心 | 会降低方差,扭曲相关性 |
| 前向/后向填充 | 时间序列数据 | 保留时序趋势 | 不适用于突变场景 |
| 插值法(线性/样条) | 连续型,有规律变化 | 平滑自然 | 计算量稍大 |
| 模型预测填充 | 缺失比例高,变量间关系复杂 | 精度高 | 容易过拟合,耗时 |
# 我常用的缺失值处理代码
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('environment_data.csv')
# 1. 查看缺失情况
print(df.isnull().sum())
# 2. 简单填充:用中位数(对异常值更鲁棒)
df['pm25'].fillna(df['pm25'].median(), inplace=True)
# 3. 时间序列用前向填充
df['temperature'].fillna(method='ffill', inplace=True)
# 4. 线性插值(适合连续变化的数据)
df['humidity'].interpolate(method='linear', inplace=True)
注意:千万别一股脑全用均值填充!我曾经见过一个项目,把风速的缺失值全填了平均值,结果原本的风速分布被压扁了,后续的风能评估完全失真。你想想看,风速是偏态分布,用均值填充等于把低频高风速事件全抹掉了。
二、异常值检测:揪出那些「不老实」的数据点
异常值,就是那些明显偏离正常范围的数据点。环境数据里,异常值可能是传感器故障、记录错误,也可能是真实的极端事件——比如突然的污染峰值。
我个人的原则是:先检测,再判断,最后处理。别一上来就删,万一那是重要的科学发现呢?
2.1 常用检测方法
方法一:IQR四分位法
这是我最喜欢的方法,简单、直观、不依赖数据分布。说白了,就是找出「离群太远」的点。
- 计算Q1(25%分位数)和Q3(75%分位数)
- IQR = Q3 - Q1
- 正常范围:[Q1 - 1.5×IQR, Q3 + 1.5×IQR]
- 超出这个范围的,标记为异常
# IQR异常值检测
Q1 = df['pm25'].quantile(0.25)
Q3 = df['pm25'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['pm25'] < lower_bound) | (df['pm25'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")
方法二:Z-Score法
适合数据近似正态分布的情况。Z-Score表示数据点偏离均值多少个标准差。通常 |Z| > 3 就认为是异常。
from scipy import stats
z_scores = np.abs(stats.zscore(df['pm25']))
outliers = df[z_scores > 3]
print(f"Z-Score法检测到 {len(outliers)} 个异常值")
避坑指南:我曾经处理某市的空气质量数据,用Z-Score法检测到一批「异常」高值。差点就删了,后来一查,原来是附近工厂夜间偷排导致的真实污染事件。所以啊,异常值不一定是「错误」,也可能是「信号」。处理前一定要结合业务背景判断。
2.2 异常值处理策略
- 确认是错误:直接删除或修正(比如传感器故障导致的负值)
- 确认是真实极端事件:保留,但单独标注
- 不确定:用盖帽法(Winsorize),把异常值替换为边界值
# 盖帽法处理:将异常值替换为边界值
df['pm25'] = df['pm25'].clip(lower=lower_bound, upper=upper_bound)
三、数据标准化与归一化:让不同量纲的数据「对话」
环境数据里,变量单位五花八门:PM2.5是μg/m³,温度是°C,风速是m/s……数值范围差好几个数量级。如果不做标准化,那些数值大的变量就会在模型里「喧宾夺主」。
嗯,这里要注意:标准化和归一化不是一回事,别搞混了。
3.1 标准化(Z-Score标准化)
把数据变成均值为0、标准差为1的分布。公式很简单:z = (x - μ) / σ
适合数据本身有异常值,或者算法假设数据服从正态分布的场景(比如线性回归、SVM)。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = pd.DataFrame(
scaler.fit_transform(df[['pm25', 'temperature', 'humidity']]),
columns=['pm25', 'temperature', 'humidity']
)
3.2 归一化(Min-Max归一化)
把数据压缩到[0, 1]区间。公式:x_norm = (x - min) / (max - min)
适合数据有明确边界,或者算法对数值范围敏感的场景(比如神经网络、KNN)。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
scaler.fit_transform(df[['pm25', 'temperature', 'humidity']]),
columns=['pm25', 'temperature', 'humidity']
)
| 方法 | 输出范围 | 对异常值敏感度 | 适用算法 |
|---|---|---|---|
| Z-Score标准化 | 无固定范围(通常-3~3) | 中等 | 线性回归、SVM、PCA |
| Min-Max归一化 | [0, 1] | 高(异常值会压缩正常值) | 神经网络、KNN、梯度下降类 |
重要提醒:标准化和归一化的参数(均值、标准差、最大最小值)一定要只用训练集计算,然后用同样的参数去转换测试集和验证集。我见过有人把整个数据集一起标准化,然后做训练测试划分——这属于数据泄露,会导致模型评估结果虚高,上线就翻车。
四、预处理流程总结
说了这么多,其实就三步:
- 先处理缺失值——别让空值干扰后续分析
- 再检测异常值——结合业务判断是删是留
- 最后标准化/归一化——让所有变量站在同一起跑线上
顺序别搞反了。你想想看,如果先标准化再处理缺失值,那些填充值会影响均值和标准差的计算,一步错步步错。
一句话总结:数据预处理不是「做完就行」,而是「做对才行」。每一步的选择都直接影响后续分析的质量。多花点时间在预处理上,后面建模会省心很多。
公众号:蓝海资料掘金营,微信deep3321