数据预处理实战:缺失值处理、异常值检测与修正、数据标准化与归一化

大家好,我是老张。干风电运维这行十几年了,说实话,最头疼的不是风机本身出故障,而是拿到的数据一塌糊涂。你想想看,SCADA系统每天吐出来几十万条数据,里面缺的缺、跳的跳,直接拿去做分析,那结果能信吗?

今天这一章,咱们就专门聊聊数据预处理。说白了,就是把脏数据洗干净,让模型能看懂、能用好。我个人习惯把预处理分成三步:先处理缺失值,再揪出异常点,最后做标准化。走完这三步,数据才算真正能用。

数据预处理核心流程 原始SCADA数据 ① 缺失值处理 ② 异常值检测与修正 ③ 标准化与归一化 删除 / 填充 / 插值 3σ / IQR / 箱线图 Z-score / Min-Max 干净、标准的数据集

一、缺失值处理——别让空值坑了你

先说说缺失值。我在项目里遇到过最夸张的一次,某台风机连续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']]
)

重要提醒:标准化参数(均值和标准差、最小值和最大值)一定要用训练集计算,然后应用到测试集上。千万别把测试集的数据混进来一起算,那叫数据泄露,模型评估结果会虚高。

嗯,到这里数据预处理的核心内容就讲完了。总结一下我的经验:缺失值处理要「看情况」,异常值检测要「结合工况」,标准化要「选对方法」。这三步走扎实了,后面的建模工作才能事半功倍。

记住一句话:数据预处理花的时间,永远值得。你省掉的每一步,都会在模型效果上还回来。


专注资料整理