3、数据预处理实战:缺失值处理、异常值检测与修正、数据标准化与归一化
大家好,我是老张。做风电AI运维这些年,我踩过最大的坑,就是数据预处理没做好。
你想想看,风机SCADA系统一天能产生上千万条数据。但这些数据里,有传感器故障导致的缺失值,有通信中断产生的异常跳变,还有不同量纲带来的计算偏差。说白了,垃圾数据进,垃圾模型出。今天这一章,我就把压箱底的数据预处理经验全盘托出。
核心观点:数据预处理占整个异常检测项目60%以上的工作量。预处理做得好,模型训练事半功倍。
3.1 缺失值处理:别让空值毁了你的模型
我在项目中遇到过最头疼的事——某风场连续3天的风速数据全部为0。一开始以为是没风,后来发现是传感器冻住了。这就是典型的缺失值问题。
风电数据缺失的原因很多:传感器故障、通信中断、数据存储异常。我个人习惯,拿到数据第一件事就是检查缺失率。
import pandas as pd
import numpy as np
# 加载风电数据
df = pd.read_csv('wind_turbine_data.csv')
# 检查缺失值概览
print("各列缺失数量:")
print(df.isnull().sum())
# 计算缺失率
missing_rate = df.isnull().sum() / len(df) * 100
print("\n缺失率(%):")
print(missing_rate)
嗯,这里要注意。缺失率低于5%的列,我一般直接删除缺失行。但缺失率超过30%的,就得慎重了。
我的经验:风速、功率这类关键参数,缺失率超过10%就别删了。用插值法补全,效果比删除好得多。
常用的缺失值处理方法,我整理了一张表:
| 方法 | 适用场景 | 代码示例 | 注意事项 |
|---|---|---|---|
| 删除法 | 缺失率<5%,随机缺失 | df.dropna() |
会损失样本量 |
| 均值填充 | 数据分布对称,无极端值 | df.fillna(df.mean()) |
会降低方差 |
| 中位数填充 | 数据有异常值或偏态分布 | df.fillna(df.median()) |
稳健性更好 |
| 前向填充 | 时间序列数据,短时间缺失 | df.fillna(method='ffill') |
适合连续变化参数 |
| 线性插值 | 风速、温度等连续变量 | df.interpolate() |
我最推荐的方法 |
# 实战:综合处理缺失值
# 1. 删除缺失率超过50%的列
df = df.dropna(thresh=len(df)*0.5, axis=1)
# 2. 风速列用线性插值
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')
# 3. 温度列用前向填充(温度变化缓慢)
df['temperature'] = df['temperature'].fillna(method='ffill')
# 4. 功率列用中位数填充(有异常值风险)
df['active_power'] = df['active_power'].fillna(df['active_power'].median())
3.2 异常值检测与修正:揪出那些捣乱的数据点
为什么异常值这么要命?我举个例子。有一次做功率预测模型,训练集里混入了一个风速80m/s、功率2000kW的异常点。结果模型学歪了,预测值整体偏高。后来排查才发现,是SCADA系统某次数据采集时发生了位翻转。
异常值检测,我常用两种方法:
方法一:3σ原则
说白了,就是假设数据服从正态分布。超出均值±3倍标准差的数据,视为异常。这个方法简单粗暴,但很有效。
def detect_outliers_3sigma(data, column):
mean = data[column].mean()
std = data[column].std()
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
outliers = data[(data[column] < lower_bound) |
(data[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 检测风速异常值
outliers, low, high = detect_outliers_3sigma(df, 'wind_speed')
print(f"风速正常范围:{low:.2f} ~ {high:.2f} m/s")
print(f"检测到异常值数量:{len(outliers)}")
方法二:IQR四分位距法
这个方法更稳健,不依赖正态分布假设。用Q1和Q3的差值来判断。
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_power, low_p, high_p = detect_outliers_iqr(df, 'active_power')
print(f"功率正常范围:{low_p:.2f} ~ {high_p:.2f} kW")
print(f"检测到异常值数量:{len(outliers_power)}")
避坑指南:我曾经在某个风场直接用3σ原则检测风速异常,结果把正常的大风天气数据全标记为异常了。为什么?因为风速分布是偏态的,不是正态分布。后来改用IQR法,效果就好多了。
检测到异常值后,怎么修正?我一般分三步走:
- 确认异常原因:是传感器故障?还是真实极端工况?
- 截尾处理:将异常值替换为边界值(上下限)
- 插值修正:对于孤立异常点,用前后正常值插值替换
# 异常值修正实战
def correct_outliers(data, column, method='clip'):
if method == 'clip':
# 截尾处理:替换为边界值
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
data[column] = data[column].clip(lower, upper)
elif method == 'interpolate':
# 插值修正:将异常值设为NaN,再插值
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
mask = (data[column] < lower) | (data[column] > upper)
data.loc[mask, column] = np.nan
data[column] = data[column].interpolate()
return data
# 对功率列进行截尾修正
df = correct_outliers(df, 'active_power', method='clip')
3.3 数据标准化与归一化:让所有特征站在同一起跑线
你想想看,风速是0-30m/s,功率是0-2000kW,温度是-20-50℃。这些数据量纲完全不同。如果不做标准化,模型会天然偏向数值大的特征。说白了,功率的2000和风速的20,在模型眼里差距巨大。
我常用的三种方法:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| Z-score标准化 | (x - μ) / σ | 无固定范围,均值为0 | 数据近似正态分布,有异常值 |
| Min-Max归一化 | (x - min) / (max - min) | [0, 1] | 数据有明确边界,无极端值 |
| Robust标准化 | (x - median) / IQR | 无固定范围 | 数据有较多异常值 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
# 1. Z-score标准化(我最常用)
scaler_std = StandardScaler()
df['wind_speed_std'] = scaler_std.fit_transform(df[['wind_speed']])
df['active_power_std'] = scaler_std.fit_transform(df[['active_power']])
# 2. Min-Max归一化(适合神经网络输入)
scaler_mm = MinMaxScaler()
df['temperature_norm'] = scaler_mm.fit_transform(df[['temperature']])
# 3. Robust标准化(数据有异常值时用)
scaler_robust = RobustScaler()
df['wind_direction_robust'] = scaler_robust.fit_transform(df[['wind_direction']])
我的建议:做风电异常检测,我一般首选Z-score标准化。原因有二:一是风电数据常有异常值,Z-score对异常值不敏感;二是很多异常检测算法(如孤立森林、LOF)都假设数据经过Z-score处理。
最后,给新手一个完整的预处理流水线:
def wind_data_preprocessing(df):
"""风电数据预处理完整流水线"""
# Step 1: 缺失值处理
# 删除缺失率高的列
df = df.dropna(thresh=len(df)*0.6, axis=1)
# 关键参数插值
for col in ['wind_speed', 'active_power', 'temperature']:
if col in df.columns:
df[col] = df[col].interpolate(method='linear')
# Step 2: 异常值检测与修正
for col in ['wind_speed', 'active_power']:
if col in df.columns:
df = correct_outliers(df, col, method='clip')
# Step 3: 标准化
scaler = StandardScaler()
feature_cols = ['wind_speed', 'active_power', 'temperature',
'wind_direction', 'rotor_speed']
available_cols = [c for c in feature_cols if c in df.columns]
df[available_cols] = scaler.fit_transform(df[available_cols])
return df, scaler
# 执行预处理
df_clean, scaler = wind_data_preprocessing(df)
print("预处理完成!数据形状:", df_clean.shape)
好了,数据预处理这块就讲到这里。记住一句话:预处理花的时间,都会在模型效果上还回来。别嫌麻烦,每一步都值得认真对待。
公众号:蓝海资料掘金营,微信deep3321