4. 数据清洗与预处理:缺失值处理、异常值检测、数据归一化与时间序列对齐
各位同行,咱们直接进入正题。数据清洗这事儿,说白了就是给数字孪生模型「喂饭」。你喂进去的是馊饭,它吐出来的肯定是垃圾。我在风场干了十几年,见过太多因为数据没洗干净导致模型翻车的案例。今天咱们就把这块硬骨头啃下来。
4.1 缺失值处理:别让空值毁了你的模型
风场数据里,缺失值太常见了。传感器故障、通讯中断、存储异常……原因五花八门。我个人习惯,拿到数据第一件事就是做缺失值普查。
核心原则:先看缺失比例,再定处理策略。缺失超过70%的特征,我建议直接扔掉。别心疼,留着也是祸害。
处理缺失值,我常用的方法就三种:
- 删除法:缺失比例低(<5%)且随机分布时,直接删行。我在河北某风场处理SCADA数据时,遇到过连续3小时全部传感器掉线,这种整段数据必须删掉,否则会污染时间序列。
- 填充法:均值/中位数填充适合平稳数据,但风速、功率这种波动大的信号,我推荐用前后时刻的线性插值。举个例子,t时刻风速缺失,就用(t-1)和(t+1)的平均值补上。
- 预测法:对于关键参数(比如齿轮箱温度),我会用其他相关变量做回归预测填充。嗯,这招精度高,但计算量也大。
# 我个人常用的缺失值处理代码片段
import pandas as pd
import numpy as np
# 读取风场SCADA数据
df = pd.read_csv('wind_farm_data.csv', parse_dates=['timestamp'])
# 检查缺失比例
missing_ratio = df.isnull().sum() / len(df) * 100
print('缺失比例:\n', missing_ratio[missing_ratio > 0])
# 线性插值填充风速和功率
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')
df['active_power'] = df['active_power'].interpolate(method='linear')
# 对于温度等缓变信号,用前向填充
df['gearbox_temp'] = df['gearbox_temp'].fillna(method='ffill')
我曾经踩过的坑:有一次用均值填充风速缺失值,结果模型训练出来预测偏差巨大。后来才发现,那段时间正好是台风过境,均值完全不能代表实际工况。所以啊,填充之前一定要看上下文,别盲目操作。
4.2 异常值检测:揪出那些「捣乱」的数据点
异常值是什么?说白了就是那些明显不符合物理规律的数据点。比如风速显示150m/s,或者功率为负值。你想想看,这明显是传感器抽风了。
我常用的异常值检测方法,按场景分三类:
| 方法 | 适用场景 | 我的经验 |
|---|---|---|
| 3σ原则 | 正态分布的数据(如环境温度) | 简单粗暴,但遇到偏态分布就失效 |
| IQR四分位法 | 非正态分布、有离群点的数据 | 我在功率曲线清洗时最爱用这个 |
| DBSCAN聚类 | 多维特征联合检测 | 计算量大,但能发现组合异常 |
这里重点说说功率曲线异常检测。风机的功率曲线是有物理边界的——切入风速以下不发电,切出风速以上停机。我在内蒙古某风场就遇到过,SCADA系统记录了一批「风速3m/s、功率2000kW」的数据,这明显是传感器串扰了。
# 基于IQR的异常值检测
def detect_outliers_iqr(data, factor=1.5):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
return (data < lower_bound) | (data > upper_bound)
# 标记异常点
df['is_outlier'] = detect_outliers_iqr(df['active_power'])
print(f'检测到 {df["is_outlier"].sum()} 个异常功率点')
小技巧:对于风场数据,我建议结合物理规则做二次过滤。比如风速-功率的散点图,超出理论功率曲线±20%范围的,基本可以判定为异常。这比纯统计方法靠谱得多。
4.3 数据归一化:把不同尺度的数据拉到同一张桌上
归一化这事儿,很多新手容易忽略。你想想看,风速是0-30m/s,温度是-20-50℃,振动加速度是0-10g,这些数据量级差了几十倍。如果不做归一化,神经网络训练时大数值特征会主导梯度更新,小数值特征直接被淹没了。
我常用的归一化方法就两种:
- Min-Max归一化:把数据映射到[0,1]区间。适合数据分布有明确边界的情况,比如风速、功率。我在做数字孪生模型时,输入层基本都用这个。
- Z-score标准化:减去均值除以标准差。适合数据分布近似正态的情况,比如振动信号。注意,标准化后的数据有正有负,不是所有模型都吃得消。
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# Min-Max归一化(推荐用于风速、功率)
scaler_minmax = MinMaxScaler()
df[['wind_speed', 'active_power']] = scaler_minmax.fit_transform(
df[['wind_speed', 'active_power']]
)
# Z-score标准化(推荐用于振动、温度)
scaler_zscore = StandardScaler()
df[['vibration_x', 'gearbox_temp']] = scaler_zscore.fit_transform(
df[['vibration_x', 'gearbox_temp']]
)
避坑指南:归一化参数(最大值、最小值、均值、标准差)一定要用训练集计算,然后应用到验证集和测试集。我曾经见过有人把全部数据一起归一化,结果模型在线上部署时直接崩了——因为新数据的范围超出了训练集的边界。
4.4 时间序列对齐:让所有数据「对表」
风场的数据源太多了。SCADA系统每10秒一条,CMS振动数据每2秒一条,气象数据每10分钟一条,还有手动记录的运维日志……这些数据的时间戳往往不同步。你想想看,如果不做对齐,模型怎么知道「这个风速对应哪个振动值」?
我个人习惯的做法是:
- 统一时间基准:选一个主时间轴,通常是SCADA的10秒间隔。因为SCADA数据最完整,覆盖所有风机。
- 重采样:把其他数据源重采样到主时间轴上。高频数据(如振动)用降采样取均值,低频数据(如气象)用前向填充或插值。
- 处理时延:不同传感器之间可能存在固定时延。比如风速仪在机舱后面,测到的风速比实际作用在叶片上的风速晚了2-3秒。这个时延需要通过互相关分析来校正。
# 时间序列对齐示例
# 假设主时间轴是SCADA的10秒间隔
main_timeline = pd.date_range(start='2024-01-01', end='2024-01-02', freq='10S')
# 振动数据重采样到10秒间隔(取均值)
vibration_resampled = vibration_data.resample('10S').mean()
# 气象数据前向填充
weather_resampled = weather_data.resample('10S').ffill()
# 对齐到主时间轴
aligned_data = pd.DataFrame(index=main_timeline)
aligned_data['wind_speed'] = scada_data['wind_speed']
aligned_data['vibration'] = vibration_resampled['vibration_x']
aligned_data['temperature'] = weather_resampled['temp']
我曾经犯过的错:在江苏某海上风场做寿命预测时,没有校正风速仪的安装位置时延,结果模型预测的疲劳载荷偏差了15%。后来用互相关函数算出来时延是2.8秒,校正后模型精度直接提升了一个档次。所以啊,时间对齐不是简单的「对表」,还要考虑物理延迟。
知识体系总览
下面这张图是我自己总结的数据清洗与预处理流程,每次做项目前我都会对照着走一遍:
好了,数据清洗这块就聊到这儿。记住一句话:干净的数据是数字孪生的基石。你在项目里花60%的时间做数据清洗,一点都不夸张。下一节咱们聊聊特征工程,看看怎么从这些干净数据里挖出真正有用的信息。