数据预处理实战:缺失值处理、异常值检测与修正、数据标准化与归一化
大家好,我是老张。干风电运维这行十几年了,说实话,最头疼的不是风机本身出故障,而是拿到的数据一塌糊涂。你想想看,SCADA系统每天吐出来几十万条数据,里面缺的缺、跳的跳,直接拿去做分析,那结果能信吗?
今天这一章,咱们就专门聊聊数据预处理。说白了,就是把脏数据洗干净,让模型能看懂、能用好。我个人习惯把预处理分成三步:先处理缺失值,再揪出异常点,最后做标准化。走完这三步,数据才算真正能用。
一、缺失值处理——别让空值坑了你
先说说缺失值。我在项目里遇到过最夸张的一次,某台风机连续3天的风速数据全是NaN。为什么会出现这种情况?传感器故障、通讯中断、存储异常,原因多了去了。但不管什么原因,空值摆在那里,模型就没法跑。
处理缺失值,我一般分三种情况:
- 直接删除:如果缺失比例很小(比如不到1%),而且数据量够大,直接删掉最省事。但要注意,别把连续缺失的整段数据都删了,那会破坏时间序列的连续性。
- 均值/中位数填充:对于功率、风速这种相对稳定的参数,用前后几小时的中位数填充,效果还不错。我个人习惯用中位数而不是均值,因为均值容易被异常值带偏。
- 插值法:对于温度、振动这类有趋势变化的参数,线性插值或样条插值更靠谱。比如机舱温度从35度突然跳到NaN,然后变成36度,中间插个35.5度,合情合理。
核心原则:缺失值处理没有银弹。一定要先看数据分布,再选方法。别上来就fillna(0),那会毁掉整个数据集。
# 我个人常用的缺失值处理代码片段
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('wind_turbine_data.csv', parse_dates=['timestamp'])
# 检查缺失情况
print(df.isnull().sum())
# 风速缺失:用前后1小时的中位数填充
df['wind_speed'] = df['wind_speed'].fillna(
df['wind_speed'].rolling(window=12, min_periods=1, center=True).median()
)
# 功率缺失:线性插值
df['active_power'] = df['active_power'].interpolate(method='linear')
# 如果连续缺失超过10个点,直接删除这段
df = df[~(df['wind_speed'].isnull() | df['active_power'].isnull())]
小技巧:处理缺失值时,建议保留一份原始数据备份。万一填充策略选错了,还能回头重来。我曾经就因为没备份,折腾了一下午才把数据恢复回来。
二、异常值检测与修正——揪出那些捣乱的
异常值比缺失值更隐蔽。你想想看,一个风速传感器偶尔抽风,报出80m/s的数值——这在海上都不可能,更别说陆上了。但程序不会自动识别,它只会老老实实把80m/s算进去,结果模型直接崩掉。
我常用的异常值检测方法有三种:
| 方法 | 适用场景 | 阈值 | 我的经验 |
|---|---|---|---|
| 3σ原则 | 正态分布的数据(如温度) | 均值±3倍标准差 | 简单粗暴,但容易被极端值影响 |
| IQR四分位距 | 偏态分布的数据(如风速) | Q1-1.5IQR 到 Q3+1.5IQR | 稳健性好,我比较常用 |
| 箱线图 | 可视化快速筛查 | 同上 | 适合先看一眼数据分布 |
举个例子。有一次我分析某风场半年的数据,发现有几天的发电功率异常偏高。用IQR一查,原来是功率传感器在校准期间输出了错误值。这些点如果不处理,模型会误以为风机性能变好了,实际上根本没那回事。
注意:异常值不一定是错误值。比如风机在切出风速附近运行,功率突然掉到零,这是正常现象。别一棍子打死所有异常点,要结合工况判断。
# 异常值检测实战代码
def detect_outliers_iqr(data, column):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 检测风速异常
outliers, low, high = detect_outliers_iqr(df, 'wind_speed')
print(f'发现 {len(outliers)} 个风速异常点')
# 修正:用上下限截断
df['wind_speed'] = df['wind_speed'].clip(lower=low, upper=high)
避坑指南:我曾经遇到过一种情况——异常值不是单个点,而是一段连续的数据。比如通讯中断后恢复,前几分钟的数据全是乱的。这时候用单个点检测没用,得用滑动窗口看局部变化率。变化率超过正常范围3倍以上的,直接标记为异常段。
三、数据标准化与归一化——让所有特征站在同一起跑线
这一步很多人容易忽略。你想想看,风速是0-30m/s,功率是0-2000kW,温度是-20到50度。这些数值的量级差了几十倍甚至上百倍。如果直接扔进模型,数值大的特征会主导学习过程,小的特征直接被淹没。
标准化和归一化,说白了就是给数据做一次「对齐」。我常用的两种方法:
- Z-score标准化:把数据变成均值为0、标准差为1的分布。适合数据本身近似正态分布的情况。比如温度、振动加速度这些。
- Min-Max归一化:把数据压缩到[0,1]区间。适合数据有明确上下界的情况。比如风速、功率这些。
我个人习惯:如果后续要用神经网络或深度学习,优先用Min-Max归一化。如果是传统机器学习模型(比如随机森林、XGBoost),其实对量纲不敏感,但为了统一处理,我还是会做Z-score标准化。
# 标准化与归一化代码
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler_std = StandardScaler()
df[['wind_speed', 'active_power', 'temperature']] = scaler_std.fit_transform(
df[['wind_speed', 'active_power', 'temperature']]
)
# Min-Max归一化
scaler_mm = MinMaxScaler()
df[['wind_speed', 'active_power', 'temperature']] = scaler_mm.fit_transform(
df[['wind_speed', 'active_power', 'temperature']]
)
重要提醒:标准化参数(均值和标准差、最小值和最大值)一定要用训练集计算,然后应用到测试集上。千万别把测试集的数据混进来一起算,那叫数据泄露,模型评估结果会虚高。
嗯,到这里数据预处理的核心内容就讲完了。总结一下我的经验:缺失值处理要「看情况」,异常值检测要「结合工况」,标准化要「选对方法」。这三步走扎实了,后面的建模工作才能事半功倍。
记住一句话:数据预处理花的时间,永远值得。你省掉的每一步,都会在模型效果上还回来。