数据预处理基础:缺失值处理、异常值检测、数据归一化与标准化方法
各位同行,大家好。今天我们来聊聊数据预处理。说实话,在风电场雷击故障诊断这个领域,我踩过的坑有一半都出在数据预处理上。你想想看,风机SCADA系统采集的数据,那叫一个「脏」——传感器漂移、通信中断、雷击瞬间的电磁干扰……这些都会让原始数据变得乱七八糟。
我个人的习惯是:拿到数据后,先别急着建模。先花70%的时间把数据洗干净。为什么?因为「垃圾进,垃圾出」。你模型再牛,喂进去的是脏数据,出来的结果也是废的。
核心观点:数据预处理不是「锦上添花」,而是「雪中送炭」。在雷击故障诊断中,预处理做得好,模型准确率能提升20%以上。
一、缺失值处理——别让「空」坑了你
雷击故障数据里,缺失值太常见了。我记得有一次,某风场的数据集里,雷电流幅值这一列有将近30%的缺失。原因很简单——雷击瞬间传感器被强电磁场干扰,直接「死机」了。
处理缺失值,我一般分三步走:
- 先诊断——看看缺失是随机的,还是系统性的
- 再决策——缺失比例低就删,比例高就补
- 最后动手——选对填充方法
我的经验:如果某列缺失超过50%,我建议直接扔掉。别心疼,留着反而是噪音。但如果缺失在20%以内,优先用插值法,尤其是时间序列数据。
常用的方法有这些:
| 方法 | 适用场景 | 缺点 |
|---|---|---|
| 删除法 | 缺失比例<5%,且随机缺失 | 样本量减少,可能丢失信息 |
| 均值/中位数填充 | 数值型特征,缺失比例低 | 会降低方差,影响模型 |
| 前向/后向填充 | 时间序列数据(如风速、功率) | 对突变不敏感 |
| 线性插值 | 连续变化的数据 | 计算量稍大 |
| KNN填充 | 特征间相关性强的场景 | 数据量大时很慢 |
代码实现其实很简单。我习惯用pandas来处理:
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('wind_turbine_data.csv')
# 查看缺失情况
print(df.isnull().sum())
# 方法1:删除缺失行(慎用)
df_clean = df.dropna(subset=['雷电流幅值'])
# 方法2:均值填充
df['风速'].fillna(df['风速'].mean(), inplace=True)
# 方法3:前向填充(时间序列常用)
df['有功功率'].fillna(method='ffill', inplace=True)
# 方法4:线性插值(我最推荐)
df['雷电流幅值'] = df['雷电流幅值'].interpolate(method='linear')
避坑指南:我曾经犯过一个错误——对雷击故障标记(0/1标签)也做了均值填充。结果模型训练出来,预测值全是小数,根本没法用。记住:分类标签的缺失,要么删,要么用众数填充,千万别用均值!
二、异常值检测——揪出「捣乱分子」
风电场的数据里,异常值特别「狡猾」。有些是传感器故障导致的,比如风速突然飙到100m/s——这明显不合理。但有些异常值,其实是雷击故障的真实信号,比如电压骤降、电流尖峰。
所以我的原则是:先理解业务,再动手检测。别一上来就把所有「离群点」都干掉,那可能会把故障特征也删掉了。
常用的检测方法:
- 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的就是异常。简单粗暴,但有效。
- 箱线图法:用四分位数来界定异常。不受极端值影响,更稳健。
- DBSCAN聚类:基于密度的聚类,把稀疏区域的点标记为异常。适合形状复杂的数据。
- 孤立森林:随机森林的变体,专门用于异常检测。高维数据表现不错。
我个人的习惯是:先用箱线图快速扫一遍,再用孤立森林做精细检测。双保险。
from sklearn.ensemble import IsolationForest
import matplotlib.pyplot as plt
# 箱线图快速查看
df.boxplot(column=['风速', '有功功率', '雷电流幅值'])
plt.show()
# 孤立森林检测
model = IsolationForest(contamination=0.05, random_state=42)
df['异常标记'] = model.fit_predict(df[['风速', '有功功率', '雷电流幅值']])
# 查看异常样本
anomalies = df[df['异常标记'] == -1]
print(f"检测到 {len(anomalies)} 个异常样本")
一个小技巧:对于雷电流幅值这种特征,我通常会结合物理知识设定上下限。比如,雷电流幅值一般在10kA到200kA之间,超出这个范围的,基本可以判定为传感器异常。这种「业务规则+算法」的组合,效果最好。
三、数据归一化与标准化——让特征「平起平坐」
你想想看,风速的单位是m/s,数值在0-30之间;有功功率的单位是kW,数值在0-2000之间。如果直接丢进模型,模型会「偏心」——它会更关注数值大的特征,而忽略数值小的特征。
归一化和标准化,说白了就是给所有特征「统一度量衡」。我常用的方法有:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Min-Max归一化 | (x - min) / (max - min) | [0, 1] | 数据有明确边界,如温度、风速 |
| Z-score标准化 | (x - μ) / σ | 无固定范围,均值为0 | 数据近似正态分布,如功率波动 |
| Robust标准化 | (x - median) / IQR | 无固定范围 | 数据有较多异常值 |
| MaxAbs归一化 | x / |max| | [-1, 1] | 稀疏数据或中心在0附近 |
嗯,这里要注意:归一化和标准化,一定要先拆分训练集和测试集,再分别做。我见过有人把整个数据集一起归一化,然后才拆分——这会导致数据泄露,模型评估结果虚高。
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
from sklearn.model_selection import train_test_split
# 拆分数据
X = df[['风速', '有功功率', '雷电流幅值']]
y = df['雷击故障标记']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 方法1:Min-Max归一化
scaler_minmax = MinMaxScaler()
X_train_scaled = scaler_minmax.fit_transform(X_train)
X_test_scaled = scaler_minmax.transform(X_test) # 注意:用训练集的参数
# 方法2:Z-score标准化
scaler_std = StandardScaler()
X_train_std = scaler_std.fit_transform(X_train)
X_test_std = scaler_std.transform(X_test)
# 方法3:Robust标准化(抗异常值)
scaler_robust = RobustScaler()
X_train_robust = scaler_robust.fit_transform(X_train)
X_test_robust = scaler_robust.transform(X_test)
避坑指南:我曾经在某个项目里,对雷击故障标签也做了归一化。结果模型输出变成了0.3、0.7这种小数,完全没法解释。记住:只对特征做归一化/标准化,标签(目标变量)不要动。除非你做的是回归任务,而且标签量级差异很大,那可以考虑对标签也做标准化,但预测完要记得反变换回来。
四、实战中的「组合拳」
在实际的风电项目中,我通常会把这三个步骤串起来,形成一个预处理流水线:
- 第一步:缺失值处理——先用线性插值填充时间序列特征,再用均值填充其他数值特征
- 第二步:异常值检测——用孤立森林标记异常,然后结合业务知识判断是保留还是删除
- 第三步:标准化——用RobustScaler,因为风电数据里异常值比较多,RobustScaler更抗造
举个例子,有一次某风场的数据集里,雷电流幅值出现了几个「0」值。按理说,雷电流不可能是0。我查了一下,发现是传感器在雷击瞬间被烧毁了,导致后续记录的全是0。这种情况,我直接把这些样本标记为「传感器故障」,从训练集中剔除。你看,这就是业务知识的重要性。
总结一下:数据预处理没有「万能公式」,关键是根据数据特点和你对业务的理解,灵活组合各种方法。多试、多看、多验证——这才是资深工程师的做法。