数据预处理实战:缺失值处理、异常值检测与标准化
数据预处理,说白了就是给模型「喂干净饭」。
我见过太多新手,模型调了半天效果上不去,最后发现是原始数据里一堆坑。嗯,今天咱们就把这些坑填平。
一、缺失值处理:别让空值毁了你的模型
风电数据里,缺失值太常见了。传感器故障、通信中断、维护停机……原因五花八门。我个人习惯,拿到数据第一件事就是检查缺失率。
核心原则:缺失率低于5%,直接删除或简单填充;超过20%,必须用插值法;超过50%,建议重新审视数据采集方案。
1. 前向填充(Forward Fill)
最简单粗暴的方法。用上一个有效值填充后面的缺失值。
为什么能用?风电数据是时间序列,相邻时刻的风速、功率变化不会太大。我曾在某风场项目中,用前向填充处理了连续3小时的缺失数据,效果居然还不错。
import pandas as pd
# 前向填充
df['wind_speed'].fillna(method='ffill', inplace=True)
# 限制填充步数,最多填充5个连续缺失
df['wind_speed'].fillna(method='ffill', limit=5, inplace=True)
注意:如果缺失值出现在数据开头,前向填充会失效。我曾经踩过这个坑,后来改用后向填充先处理开头部分。
2. 插值法(Interpolation)
插值法更精细。它利用缺失点前后的数据,拟合出一条曲线来估算缺失值。
# 线性插值
df['wind_speed'].interpolate(method='linear', inplace=True)
# 时间插值(考虑时间间隔)
df['wind_speed'].interpolate(method='time', inplace=True)
# 多项式插值(阶数不宜过高)
df['wind_speed'].interpolate(method='polynomial', order=2, inplace=True)
我个人偏好时间插值。为什么?因为风电数据的时间戳往往不均匀,时间插值能更准确地反映真实变化趋势。
避坑指南:我曾经用高阶多项式插值处理一段剧烈波动的风速数据,结果插出了负值。嗯,物理上不可能。后来我改用线性插值加限幅处理,才解决了问题。
二、异常值检测与修正:揪出那些「离谱」的数据
异常值比缺失值更隐蔽。它看起来像正常数据,但实际是错的。比如风速突然飙到100m/s,或者功率超过额定值两倍——这些明显不合理。
1. 3-Sigma 法则
假设数据服从正态分布,超出均值±3倍标准差的值视为异常。
import numpy as np
def detect_outliers_3sigma(data, col):
mean = data[col].mean()
std = data[col].std()
lower = mean - 3 * std
upper = mean + 3 * std
outliers = data[(data[col] < lower) | (data[col] > upper)]
return outliers.index.tolist()
# 使用
outlier_idx = detect_outliers_3sigma(df, 'wind_speed')
但要注意,风电数据往往不是严格的正态分布。我遇到过风速数据呈双峰分布的情况,3-Sigma 会把很多正常值误判为异常。
2. IQR 方法(四分位距法)
IQR 不依赖正态分布假设,更稳健。它用四分位数来界定异常范围。
def detect_outliers_iqr(data, col):
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = data[(data[col] < lower) | (data[col] > upper)]
return outliers.index.tolist()
# 使用
outlier_idx = detect_outliers_iqr(df, 'power')
我的经验:对于风电功率数据,IQR 比 3-Sigma 更靠谱。因为功率数据往往有上下限(0到额定功率),IQR 能更好地适应这种有界分布。
3. 异常值修正
检测到异常值后,别急着删除。我建议先看看能不能修正。
# 方法1:用中位数替换
df.loc[outlier_idx, 'wind_speed'] = df['wind_speed'].median()
# 方法2:用前后均值替换
for idx in outlier_idx:
if idx > 0 and idx < len(df)-1:
df.loc[idx, 'wind_speed'] = (df.loc[idx-1, 'wind_speed'] + df.loc[idx+1, 'wind_speed']) / 2
# 方法3:用插值法修正
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')
我个人习惯用插值法修正异常值。为什么?因为它能保持数据的时序连续性,不会像中位数替换那样引入突兀的跳变。
曾经踩过的坑:有一次我批量删除了所有异常值,结果模型训练时因为样本量骤减,性能反而下降了。后来我改成先修正、再删除的策略——能修正的尽量修正,实在离谱的才删。
三、数据标准化与归一化:让所有特征站在同一起跑线
风速、温度、湿度、功率……这些特征的量纲完全不同。风速是m/s,温度是℃,功率是kW。如果不做标准化,模型会天然偏向数值大的特征。
1. 标准化(Z-score Normalization)
将数据转换为均值为0、标准差为1的分布。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = pd.DataFrame(
scaler.fit_transform(df[['wind_speed', 'power', 'temperature']]),
columns=['wind_speed', 'power', 'temperature']
)
标准化适合数据近似正态分布的场景。我一般在做线性模型或神经网络时用标准化。
2. 归一化(Min-Max Scaling)
将数据缩放到[0, 1]区间。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
scaler.fit_transform(df[['wind_speed', 'power', 'temperature']]),
columns=['wind_speed', 'power', 'temperature']
)
归一化适合数据有明确上下界的场景。比如功率值,天然在0到额定功率之间,用归一化就很自然。
我的建议:如果你不确定用哪个,先试试标准化。它在大多数场景下表现更稳定。归一化对异常值敏感,如果数据里有离群点,归一化会把正常值压缩到很小的区间。
3. 什么时候不做标准化?
你可能会问:是不是所有模型都需要标准化?
不是。树模型(随机森林、XGBoost、LightGBM)对特征尺度不敏感,标准化反而没用。我刚开始做风电预测时,给XGBoost做了标准化,结果性能一点没变——白费功夫。
总结一下:
- 线性模型、神经网络、SVM → 必须标准化/归一化
- 树模型 → 不需要
- KNN、聚类 → 建议做,否则距离计算会出问题
四、知识体系总览
下面这张图,把本章的核心逻辑串起来了。你想想看,数据预处理其实就三步:填坑(缺失值)、排雷(异常值)、统一尺度(标准化)。
嗯,数据预处理这块,说难不难,说简单也不简单。关键是要理解每个方法背后的假设和适用场景。我在项目中见过太多人盲目套用方法,结果适得其反。
记住一句话:没有最好的方法,只有最合适的方法。多试、多对比、多思考,你也能成为数据预处理的高手。