4. 数据采集与预处理:SCADA数据清洗、异常值检测与时间同步

各位同行,咱们今天聊聊数据预处理。说实话,我在风电行业摸爬滚打这么多年,见过太多「模型建得挺好,一上数据就崩」的案例。问题出在哪?十有八九是数据没洗干净。

SCADA系统每分钟都在吐数据,但你要知道,这些数据不是拿来就能用的。传感器漂移、通讯中断、风机停机……各种情况都会把数据搞脏。我习惯把预处理分成三步走:清洗、检测、同步。咱们一个一个来。

4.1 SCADA数据清洗:把「垃圾」筛出去

数据清洗,说白了就是去掉那些明显不合理的数据。你想想看,风速显示-5m/s,这合理吗?肯定不合理。功率曲线图上出现一个点在左上角,风速2m/s功率却到了1500kW,这明显是传感器抽风了。

我一般会做这几件事:

  • 范围检查:每个物理量都有合理范围。风速0-40m/s,功率0-额定功率×1.1,转速0-额定转速×1.2。超出范围的直接剔除。
  • 变化率检查:风速1秒内从5m/s跳到25m/s?不可能。我设一个最大变化率阈值,比如风速每秒不超过5m/s。
  • 重复值处理:连续10个采样点数值完全一样?传感器大概率卡住了。这种数据要标记出来。
  • 缺失值填充:短时间缺失(比如5分钟以内)可以用插值法。长时间缺失?我建议直接丢弃那一段。

重要提醒:清洗不是越狠越好。我曾经在一个项目里把阈值设得太严,结果把正常的数据也砍掉了,模型训练出来偏差很大。后来我学乖了——先做统计分布,看看数据的正常范围,再设阈值。

4.2 异常值检测:揪出「潜伏者」

清洗解决的是明显错误,但有些异常值藏得很深。比如叶片结冰导致功率下降,数据看起来「合理」但「不正常」。这时候就需要更高级的检测方法。

我常用的方法有几种:

  1. 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的就是异常。简单粗暴,但对付功率、转速这类数据挺好用。
  2. 箱线图法:用四分位数来判定。Q1-1.5×IQR和Q3+1.5×IQR之外的点算异常。这个方法对非正态分布的数据更友好。
  3. 局部异常因子(LOF):看一个点周围邻居的密度。密度明显低于邻居的,就是异常点。这个方法能发现「孤立」的异常。

我的经验:别只用一种方法。我习惯把3σ和箱线图结合起来用。先跑一遍3σ,再跑一遍箱线图,取两者的交集作为最终异常点。这样误报率会低很多。

为什么会这样?因为不同方法对「异常」的定义不一样。3σ对极端值敏感,箱线图对分布形态敏感。结合起来,相当于双重保险。

4.3 时间同步:让数据「对齐」

这一步很多人会忽略,但恰恰是数字孪生的关键。SCADA数据来自不同传感器,每个传感器有自己的时钟。风速仪的时间可能比功率传感器慢了2秒,你想想看,风速和功率对不上,模型怎么校准?

时间同步我一般做这几件事:

  • 统一时间基准:所有数据都转成UTC时间,避免时区混乱。
  • 重采样:SCADA数据通常是1秒或10秒一个点,但不同传感器采样频率可能不一样。我统一重采样到10秒间隔,用线性插值或最近邻插值。
  • 时延补偿:风速变化到功率响应,中间有物理延迟。我通过互相关分析找到这个延迟量,然后对齐。

注意:时间同步不是简单的「对齐时间戳」。我曾经遇到一个项目,两台风机相距500米,风速数据差了3秒。后来发现是通讯线路的延迟。这种问题不解决,模型精度永远上不去。

4.4 知识体系总览

下面这张图把数据采集与预处理的流程串起来了。你可以把它当作一个检查清单,做项目时对着走一遍。

数据采集与预处理流程 原始SCADA数据 数据清洗:范围检查、变化率、重复值 异常检测:3σ、箱线图、LOF 时间同步:重采样、时延补偿 干净、同步的数据 Step 1 Step 2 Step 3 Step 4 Step 5

4.5 代码示例:一个简单的数据清洗流程

光说不练假把式。我写了一段Python代码,展示怎么用pandas做数据清洗。你可以在自己的项目里直接改改用。

import pandas as pd
import numpy as np

# 读取SCADA数据
df = pd.read_csv('scada_data.csv', parse_dates=['timestamp'])

# 1. 范围检查
df = df[(df['wind_speed'] >= 0) & (df['wind_speed'] <= 40)]
df = df[(df['active_power'] >= 0) & (df['active_power'] <= 2000)]

# 2. 变化率检查
df['ws_diff'] = df['wind_speed'].diff().abs()
df = df[df['ws_diff'] <= 5]  # 风速变化率不超过5m/s

# 3. 重复值检测
df = df[df['wind_speed'].rolling(10).std() > 0.01]  # 连续10点标准差接近0则剔除

# 4. 缺失值填充(线性插值)
df = df.set_index('timestamp').resample('10S').mean()
df = df.interpolate(method='linear')

# 5. 异常值检测(3σ)
mean_power = df['active_power'].mean()
std_power = df['active_power'].std()
df = df[(df['active_power'] >= mean_power - 3*std_power) & 
        (df['active_power'] <= mean_power + 3*std_power)]

print(f"清洗后数据量:{len(df)} 条")

小技巧:代码里的阈值别写死。我习惯把阈值放在配置文件里,不同项目调一调就行。另外,记得保留原始数据的备份,万一洗坏了还能恢复。

4.6 常见问题与避坑指南

最后,我分享几个实际项目中踩过的坑:

  • 坑一:忽略停机数据。风机停机时,风速、功率都是0,但这些数据不能用来训练模型。我一般用状态码过滤掉停机时段。
  • 坑二:时间戳格式不统一。有的传感器用Unix时间戳,有的用字符串。统一转成datetime对象再处理。
  • 坑三:过度插值。缺失超过30%的数据,别插值了,直接丢弃那一段。插出来的数据是假的,模型学了反而坏事。

嗯,数据预处理这块就聊到这。记住一句话:数据质量决定模型上限。花80%的时间做预处理,剩下20%做建模,这个比例我用了十年,没出过大问题。

专注资料整理