2. 数据采集与清洗:SCADA数据、NWP数据、测风塔数据的获取与预处理

做风电功率预测,说白了就是跟数据打交道。我入行那会儿,最头疼的不是模型调参,而是数据本身。你想想看,模型再牛,喂进去的是垃圾,吐出来的只能是更垃圾的预测结果。

这一章,咱们就聊聊数据采集与清洗。我会把SCADA、NWP、测风塔这三类数据怎么拿、怎么洗、怎么对齐,掰开揉碎了讲清楚。

2.1 三类数据源,各有各的脾气

先说说这三类数据都是什么来头。

  • SCADA数据:风电场自己的监控系统。记录每台风机的实时运行状态。包括有功功率、风速、桨距角、发电机转速等等。采样频率一般是秒级或分钟级。
  • NWP数据:数值天气预报。气象局或者商业气象服务商提供的。包含未来几天的风速、风向、温度、气压等。空间分辨率几公里到几十公里不等,时间分辨率通常是1小时或3小时。
  • 测风塔数据:风电场选址时立的塔。测量不同高度的风速、风向。数据质量最高,但成本也高,一般只有几个塔。

这三类数据,就像三个性格迥异的同事。SCADA数据量大但噪声多,NWP数据覆盖广但精度糙,测风塔数据准但覆盖窄。你得学会跟它们打交道。

核心观点:数据采集不是简单的“拉个文件”,而是建立一套可靠的数据管道。我见过太多项目,模型调得飞起,最后发现数据延迟了2小时,预测结果毫无意义。

2.2 SCADA数据采集:从风机到数据库

SCADA数据的采集,通常走OPC协议。风机的PLC把数据发到OPC Server,然后我们写个客户端去订阅。

我个人习惯用Python的opcua库或者pyOPC。下面是一个简单的采集脚本框架:

import opcua
import time
import pandas as pd

# 连接OPC Server
client = opcua.Client("opc.tcp://192.168.1.100:4840")
client.connect()

# 订阅风机变量
node_ids = {
    "wind_speed": "ns=2;s=WT001.WindSpeed",
    "power": "ns=2;s=WT001.ActivePower",
    "pitch_angle": "ns=2;s=WT001.PitchAngle"
}

data_buffer = []

while True:
    timestamp = pd.Timestamp.now()
    row = {"timestamp": timestamp}
    for name, node_id in node_ids.items():
        node = client.get_node(node_id)
        row[name] = node.get_value()
    data_buffer.append(row)
    
    # 每10秒写入一次数据库
    if len(data_buffer) >= 10:
        df = pd.DataFrame(data_buffer)
        df.to_sql("scada_raw", con=engine, if_exists="append", index=False)
        data_buffer.clear()
    
    time.sleep(1)

我的经验:千万别在采集脚本里做复杂清洗。采集只管收数据,清洗交给下游。我曾经犯过这个错,在采集端做了异常值过滤,结果把真实的风切变事件给滤掉了,排查了三天才找到原因。

2.3 NWP数据获取:API与文件解析

NWP数据获取,现在主流是走API。比如欧洲中期天气预报中心(ECMWF)的API,或者国内的气象服务商。

嗯,这里要注意。NWP数据通常以GRIB2格式下发。这种格式不是直接能读的,需要专门的解析库。

import xarray as xr
import cfgrib

# 读取GRIB2文件
ds = xr.open_dataset("ecmwf_forecast.grib2", engine="cfgrib")

# 提取指定经纬度的风速
lat, lon = 40.5, 116.3
wind_speed = ds["u10"].sel(latitude=lat, longitude=lon, method="nearest")

为什么用xarray?因为它天然支持多维数组,NWP数据正好是四维的(时间、高度、纬度、经度)。

避坑指南:我曾经遇到过NWP数据的时间戳是UTC,而SCADA数据是本地时间。直接合并后,预测结果偏差了8小时。所以,数据对齐的第一步,永远是统一时区。

2.4 测风塔数据:小而精的参考标准

测风塔数据,通常以CSV或Excel文件形式提供。采集频率一般是10分钟一次。

测风塔的数据质量最高,但量小。我一般用它来做两件事:

  • 作为SCADA风速的校准基准
  • 验证NWP数据的空间插值精度

举个例子,如果测风塔在80米高度测到风速8m/s,而SCADA上报的是7.2m/s,那就要检查SCADA的风速仪是不是需要校准了。

2.5 数据清洗:脏数据的三座大山

数据拿到手,别急着建模。先看看数据有多脏。我总结了三类常见问题:

问题类型 表现 处理方法
缺失值 时间戳跳变,字段为空 线性插值、前向填充、或丢弃
异常值 风速为负、功率超过额定值 阈值过滤、3σ原则、IQR方法
重复值 同一时间戳出现多条记录 去重,保留最后一条或平均值

下面是我常用的清洗流水线:

def clean_scada(df):
    # 1. 去重
    df = df.drop_duplicates(subset="timestamp", keep="last")
    
    # 2. 排序
    df = df.sort_values("timestamp")
    
    # 3. 缺失值处理
    df = df.interpolate(method="linear", limit=5)
    
    # 4. 异常值过滤
    df = df[(df["wind_speed"] >= 0) & (df["wind_speed"] <= 40)]
    df = df[(df["power"] >= 0) & (df["power"] <= rated_power * 1.1)]
    
    # 5. 重采样到统一频率(比如10分钟)
    df = df.set_index("timestamp").resample("10T").mean()
    
    return df

关键点:清洗不是越狠越好。保留合理的波动,去掉明显的错误。我见过有人把风速大于25m/s的数据全删了,结果台风天直接没有预测数据,运维调度完全抓瞎。

2.6 数据对齐:让三路数据说同一种语言

三类数据的时间分辨率不一样。SCADA是分钟级,NWP是小时级,测风塔是10分钟级。怎么对齐?

我的做法是:以NWP的时间点为基准,把SCADA和测风塔数据聚合到NWP的时间戳上。

# 假设NWP数据每3小时一次
nwp_times = pd.date_range("2024-01-01", "2024-01-02", freq="3H")

# SCADA数据聚合到NWP时间点
scada_agg = scada_df.resample("3H", on="timestamp").mean()

# 合并
merged_df = pd.merge_asof(
    nwp_df, scada_agg,
    left_on="timestamp", right_on="timestamp",
    direction="nearest",
    tolerance=pd.Timedelta("1.5H")
)

为什么会用merge_asof?因为SCADA和NWP的时间戳不可能完全对齐,用最近邻匹配最合理。

2.7 知识体系总览

下面这张图,概括了本章的核心逻辑。数据从三个源头流入,经过采集、清洗、对齐,最终汇入统一的特征库。

SCADA数据 NWP数据 测风塔数据 数据采集层(OPC/API/文件解析) 数据清洗层(去重/插值/异常过滤) 数据对齐层(时间对齐/空间插值/特征融合) 输出:统一时间戳、统一频率的特征数据集

我的习惯:每次做完数据清洗,我都会跑一个数据质量报告。统计缺失率、异常率、时间覆盖度。这个报告能帮你快速定位问题,也方便跟团队沟通。别问我怎么知道的,被坑多了自然就长记性了。

好了,数据采集与清洗这块,核心就是这些。记住一句话:数据质量决定了模型的天花板。把数据搞干净了,模型训练就是水到渠成的事。

专注资料整理