第四章:数据预处理实战——缺失值处理、异常值检测、数据归一化与标准化、时间序列对齐

各位同行,大家好。欢迎来到数据预处理实战这一章。

说实话,在风机数字孪生这个领域摸爬滚打这么多年,我最大的体会就是:模型校准的成败,往往不取决于算法多高级,而取决于数据预处理做得有多扎实。你想想看,如果喂给模型的数据本身就是脏的、乱的、不对齐的,那再牛的神经网络也白搭。我个人习惯把数据预处理比作「打地基」——地基没打好,楼盖得再高也是危楼。

好,咱们直接进入正题。这一章我会带着大家,把风机SCADA数据从原始状态一步步清洗成可供数字孪生模型使用的标准数据集。

4.1 缺失值处理:别让「空」坑了你

风机SCADA系统采集数据时,经常会出现缺失值。原因很多:传感器故障、通讯中断、存储异常……我在项目中遇到过最夸张的一次,某台风机连续3天的风速数据全是空的,排查下来发现是PLC的一个接线端子松了。

缺失值处理,说白了就三个思路:删、补、插

4.1.1 直接删除法

如果缺失比例很小(比如低于5%),而且缺失是随机发生的,直接删掉这些样本是最省事的办法。但要注意——千万别删连续缺失的片段,尤其是风速、功率这些关键参数。我曾经见过有人把连续1小时的缺失数据全删了,结果模型训练出来,功率曲线中间直接断了一截,完全没法用。

⚠️ 避坑指南: 我曾经吃过一次亏——某台风机的振动传感器在低风速时段频繁缺失数据,我直接删掉了所有缺失样本。结果模型在低风速段的预测误差飙升了30%。后来才发现,缺失不是随机的,而是跟风速有关。这种「非随机缺失」千万不能简单删除。

4.1.2 均值/中位数填充法

对于数值型参数(如风速、功率、转速),可以用该参数的历史均值或中位数来填充。我个人习惯用中位数——因为风速数据经常有极端值,均值会被拉偏,中位数更稳健。

# Python示例:中位数填充
import pandas as pd
import numpy as np

# 读取原始SCADA数据
df = pd.read_csv('wind_turbine_data.csv')

# 对风速列进行中位数填充
wind_median = df['wind_speed'].median()
df['wind_speed'].fillna(wind_median, inplace=True)

# 对功率列进行中位数填充(按工况分组更合理)
df['power'] = df.groupby('operating_mode')['power'].transform(
    lambda x: x.fillna(x.median())
)

4.1.3 插值法

对于时间序列数据,插值法往往比简单填充更合理。因为风机的参数变化是有连续性的——前一秒风速是8m/s,下一秒突然变成0,这显然不合理。线性插值、样条插值、前向填充,都是常用手段。

💡 我的经验: 对于风速、功率这类变化较平缓的参数,用线性插值就够了。但对于振动信号这种高频变化的参数,我建议用前向填充(ffill)——因为振动数据一旦缺失,前后值可能差异很大,线性插值反而会引入虚假的中间值。

4.2 异常值检测:揪出那些「捣乱」的数据点

风机运行数据里,异常值比比皆是。传感器瞬间跳变、通讯干扰、风机启停机瞬间的异常波动……这些数据如果不处理,模型会被带偏。

我常用的异常值检测方法有三种,咱们一个一个说。

4.2.1 3σ原则(拉依达准则)

假设数据服从正态分布,那么超出均值±3倍标准差的数据点,就可以视为异常。这个方法简单粗暴,但有个前提——数据得大致符合正态分布。风机功率数据在额定功率附近是截断的,用3σ就不太合适。

# 3σ异常检测
mean = df['wind_speed'].mean()
std = df['wind_speed'].std()
threshold = 3 * std

# 标记异常值
df['is_outlier_3sigma'] = (np.abs(df['wind_speed'] - mean) > threshold)

4.2.2 IQR(四分位距)法

这个方法不依赖正态分布假设,更鲁棒。计算数据的Q1(25%分位数)和Q3(75%分位数),IQR = Q3 - Q1。通常认为小于Q1 - 1.5×IQR或大于Q3 + 1.5×IQR的数据是异常值。

🔑 核心要点: 我在实际项目中,对于风速、功率这类参数,习惯用IQR法。因为风机数据经常有偏态分布,IQR法比3σ更靠谱。但要注意——对于振动加速度这类参数,异常值往往意味着真正的故障前兆,不能一刀切地剔除,需要结合工况判断。

4.2.3 基于物理约束的检测

这是风机领域特有的方法。说白了,就是利用风机的物理极限来判定异常。比如:

  • 风速不可能为负值,也不可能超过切出风速(通常25m/s)
  • 功率不可能超过额定功率的1.2倍(考虑过载保护)
  • 转速不可能超过安全转速上限

这些物理约束,比任何统计方法都可靠。我建议大家在用统计方法之前,先用物理约束过滤一遍。

# 物理约束过滤
df_valid = df[
    (df['wind_speed'] >= 0) &
    (df['wind_speed'] <= 25) &
    (df['power'] >= 0) &
    (df['power'] <= 1.2 * rated_power) &
    (df['rotor_speed'] >= min_speed) &
    (df['rotor_speed'] <= max_speed)
]

4.3 数据归一化与标准化:让所有参数「平起平坐」

风机SCADA数据里,不同参数的量纲差异巨大。风速是0~25m/s,功率是0~2000kW,振动加速度是0~10m/s²……如果不做归一化或标准化,模型会天然地「偏爱」数值大的参数,数值小的参数直接被忽略。

4.3.1 Min-Max归一化

把数据缩放到[0,1]区间。公式很简单:X_norm = (X - X_min) / (X_max - X_min)。

这个方法适合数据分布比较均匀、没有极端值的情况。但有个坑——如果新来的数据超出了原来的最大值或最小值,归一化后的值就会超出[0,1]范围。我在做在线监测时就遇到过这个问题,后来改用标准化了。

# Min-Max归一化
from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df[['wind_speed', 'power', 'rotor_speed']] = scaler.fit_transform(
    df[['wind_speed', 'power', 'rotor_speed']]
)

4.3.2 Z-score标准化

把数据转换成均值为0、标准差为1的分布。公式:X_std = (X - μ) / σ。

我个人更偏爱Z-score标准化,尤其是当数据存在异常值时。因为Min-Max归一化会被极端值「压扁」正常数据,而Z-score对异常值的敏感度相对较低。

💡 实战建议: 对于振动信号、温度信号这类可能包含故障信息的参数,我建议用Z-score标准化。因为故障往往表现为偏离正常均值的异常波动,Z-score能更好地保留这种偏离信息。而对于风速、功率这类用于功率曲线建模的参数,Min-Max归一化就够用了。

4.3.3 什么时候不做归一化?

嗯,这里要特别提一句——不是所有模型都需要归一化。树模型(随机森林、XGBoost)基于分裂规则,不受量纲影响。但神经网络、SVM、KNN这些基于距离或梯度的模型,归一化是必须的。我在做数字孪生模型时,通常用LSTM或CNN,所以归一化是标配。

4.4 时间序列对齐:让不同来源的数据「对上表」

这是数据预处理里最容易被忽视、但坑最多的一步。风机的SCADA数据、振动监测数据、气象数据,往往来自不同的采集系统,时间戳可能不一致。有的系统是10秒采集一次,有的是1分钟,还有的是事件触发采集。

时间序列对齐,说白了就是把这些不同频率、不同起始时间的数据,统一到同一个时间轴上。

4.4.1 重采样(Resampling)

把高频数据降采样到低频,或者把低频数据升采样到高频。对于风机数据,我通常统一到10分钟平均值——这是行业标准,也是功率曲线测试的推荐时间尺度。

# 重采样到10分钟平均值
df_resampled = df.set_index('timestamp').resample('10T').mean()

# 注意:对于离散型参数(如状态码),用前向填充
df_status = df.set_index('timestamp')['status_code'].resample('10T').ffill()

4.4.2 时间戳标准化

不同系统的时间戳格式可能不同:有的用Unix时间戳,有的用ISO格式,有的甚至用字符串。第一步,先把所有时间戳统一成datetime格式。

⚠️ 避坑指南: 我曾经遇到过一个项目,SCADA系统用的是UTC时间,而振动监测系统用的是本地时间(UTC+8)。两个系统对不齐,导致模型训练出来的结果完全不对。后来花了整整两天才排查出来。所以,时间戳标准化时,一定要确认时区是否一致

4.4.3 处理时间偏移

有时候,两个系统虽然采样频率相同,但存在固定的时间偏移(比如SCADA系统比振动系统慢了5秒)。这种偏移可以通过互相关分析来检测和校正。

# 互相关分析检测时间偏移
from scipy import signal

# 假设两个系统都采集了功率数据
power_scada = df_scada['power'].values
power_vibration = df_vibration['power'].values

# 计算互相关
correlation = signal.correlate(power_scada, power_vibration, mode='same')
lag = np.argmax(correlation) - len(power_scada) // 2

print(f'检测到的时间偏移:{lag} 个采样点')

4.5 本章知识体系总览

说了这么多,我画了一张图,把数据预处理的整个流程串起来。这样大家脑子里能有个整体框架。

风机数据预处理实战流程 原始SCADA数据 缺失值处理 删除法 填充法 插值法 异常值检测 3σ原则 IQR法 物理约束 归一化 / 标准化 Min-Max Z-score 时间序列对齐

4.6 实战小结

好了,这一章的内容就到这里。咱们回顾一下核心要点:

  • 缺失值处理:先判断缺失类型(随机/非随机),再选方法。非随机缺失别乱删。
  • 异常值检测:物理约束优先,统计方法辅助。别把故障前兆当异常剔除了。
  • 归一化/标准化:看模型选方法。神经网络用Min-Max或Z-score都行,树模型可以跳过。
  • 时间序列对齐:统一时区、统一频率、检查偏移。这一步做不好,后面全白搭。

数据预处理没有银弹,每个风场、每台风机都有自己的「脾气」。我的建议是:先跑一遍标准流程,然后盯着可视化结果看——数据分布是否合理?时间序列是否连续?异常点是否被合理处理?多花点时间在预处理上,后面模型校准会轻松很多。


专注资料整理