4. 数据清洗与预处理:异常值检测、缺失值填充、时间戳对齐与重采样

数据清洗这事儿,说实话,是风场数字孪生里最磨人、但也最关键的环节。我见过太多项目,算法模型调得再漂亮,数据一进去就崩了——为什么?因为原始数据太“脏”了。今天咱们就把这块硬骨头啃下来。

4.1 异常值检测:别让一个坏点带偏整个模型

风场数据里,异常值太常见了。传感器故障、通信丢包、雷击干扰……随便一个原因,就能给你蹦出个离谱的数字。比如风速突然飙到80m/s,或者功率曲线完全脱离物理规律。

我个人习惯,先做物理限值检测。说白了,就是根据风机的设计参数,设定一个合理的上下界。比如某款风机额定风速12m/s,切出风速25m/s,那超过30m/s的数据基本可以判定为异常。

核心思路: 异常值检测不是越复杂越好。先做简单的规则过滤,再用统计方法或机器学习方法做精细检测。

我常用的方法有这几种:

  • 3σ原则:假设数据服从正态分布,超出均值±3倍标准差的值视为异常。适合风速、温度这类分布相对对称的变量。
  • 箱线图法(IQR):用四分位距来判定。Q1-1.5×IQR 到 Q3+1.5×IQR 之外的点是异常。这个方法对偏态分布更鲁棒。
  • DBSCAN聚类:基于密度的聚类算法,能自动识别孤立点。我在处理SCADA数据时经常用,效果不错。
# 一个简单的3σ异常检测示例
import numpy as np

def detect_outliers_3sigma(data, threshold=3):
    mean = np.mean(data)
    std = np.std(data)
    lower = mean - threshold * std
    upper = mean + threshold * std
    outliers = (data < lower) | (data > upper)
    return outliers

# 风速数据示例
wind_speed = np.array([5.2, 6.1, 4.8, 7.3, 5.9, 45.2, 6.0, 5.5])
outliers = detect_outliers_3sigma(wind_speed)
print(f"异常值索引: {np.where(outliers)[0]}")
# 输出: 异常值索引: [5]  —— 45.2m/s明显异常
我的经验: 别一上来就删数据。先标记异常,分析原因。如果是传感器瞬时抖动,可以用前后值插值替换;如果是系统性故障,那得追溯到源头修复。

4.2 缺失值填充:空着不是办法,但乱填更危险

风场数据缺失太普遍了。网络断了、存储满了、采集卡重启……各种原因。缺失率在5%以内算正常,超过10%就得警惕了。

填充方法怎么选?我一般按这个优先级来:

  1. 前向填充(ffill):用上一个有效值填充。适合短时间缺失,比如几秒钟的数据丢包。
  2. 线性插值:用前后两个有效值做线性拟合。适合变化平缓的变量,比如环境温度。
  3. 时间序列插值:考虑时间权重,用更复杂的插值方法(如样条插值)。适合风速这种波动较大的数据。
  4. 模型预测填充:用其他相关变量(如功率、桨距角)来预测缺失值。这是最后的手段,计算量大,但精度高。
注意: 千万别用全局均值填充!风场数据有很强的时序性和周期性,用均值填充会破坏数据的动态特征,后续分析全白费。

我曾经接手过一个项目,运维团队用均值填充了连续3小时的缺失风速数据。结果呢?功率预测模型直接偏了15%,排查了两天才找到原因。嗯,从那以后我特别强调:填充方法必须与缺失模式匹配

import pandas as pd
import numpy as np

# 模拟10分钟的风速数据(1分钟间隔)
time_index = pd.date_range('2024-01-01 00:00', periods=10, freq='1min')
wind_speed = [5.2, 6.1, np.nan, np.nan, 5.9, 6.0, np.nan, 6.3, 6.1, 5.8]
df = pd.DataFrame({'风速': wind_speed}, index=time_index)

# 前向填充
df['风速_ffill'] = df['风速'].ffill()

# 线性插值
df['风速_interp'] = df['风速'].interpolate(method='linear')

print(df)
# 输出对比:
#                     风速  风速_ffill  风速_interp
# 2024-01-01 00:00:00  5.2       5.2          5.2
# 2024-01-01 00:01:00  6.1       6.1          6.1
# 2024-01-01 00:02:00  NaN       6.1          6.0  (插值更合理)
# 2024-01-01 00:03:00  NaN       6.1          5.95
# 2024-01-01 00:04:00  5.9       5.9          5.9

4.3 时间戳对齐:多源数据要“说同一种语言”

风场里数据源太多了。SCADA系统、CMS振动监测、气象塔、功率预测系统……每个系统都有自己的时间戳格式和采样频率。有的用UTC,有的用本地时间,有的甚至不带时区信息。

时间戳对齐,说白了就是让所有数据在同一个时间轴上“对齐”。我一般分三步走:

  • 标准化时间格式:全部转为UTC时间,统一用ISO 8601格式(如2024-01-01T00:00:00Z)。
  • 处理时区问题:中国风场用UTC+8,但数据库可能存的是UTC。一定要明确时区,否则差8小时,分析全乱套。
  • 对齐到统一时间网格:比如所有数据都对齐到整分钟或整秒。用resample或asfreq方法。
关键点: 时间戳对齐不是简单的“对一下表”。要考虑采样频率不一致、延迟、丢包等问题。我建议先做时间戳质量评估,再决定对齐策略。
# 时间戳对齐示例
import pandas as pd

# 模拟两个数据源
scada_time = pd.date_range('2024-01-01 00:00:00', periods=5, freq='10s')
cms_time = pd.date_range('2024-01-01 00:00:05', periods=5, freq='10s')  # 延迟5秒

scada_data = pd.DataFrame({'功率': [1500, 1520, 1510, 1530, 1525]}, index=scada_time)
cms_data = pd.DataFrame({'振动': [0.5, 0.6, 0.55, 0.65, 0.6]}, index=cms_time)

# 对齐到整10秒网格
aligned_scada = scada_data.resample('10s').mean()
aligned_cms = cms_data.resample('10s').mean()

# 合并
merged = pd.concat([aligned_scada, aligned_cms], axis=1)
print(merged)
# 输出:
#                     功率   振动
# 2024-01-01 00:00:00  1500  0.5
# 2024-01-01 00:00:10  1520  0.6
# 2024-01-01 00:00:20  1510  0.55

4.4 重采样:让数据“步调一致”

重采样,就是把数据从一个频率转换到另一个频率。比如SCADA数据是1秒一条,气象数据是10分钟一条,你要做联合分析,就得统一频率。

重采样有两种方向:

  • 降采样(Downsampling):从高频到低频。比如把1秒数据聚合成1分钟均值。常用方法:mean、median、min、max。
  • 升采样(Upsampling):从低频到高频。比如把10分钟数据插值到1分钟。常用方法:ffill、bfill、interpolate。
我的建议: 降采样时,别只用均值。风场数据波动大,均值会抹掉很多细节。我常用“均值+标准差”或“均值+极值”的组合,保留更多信息。

举个例子,你想想看,风速的1分钟均值可能是6m/s,但实际波动范围是3-9m/s。如果只保留均值,湍流强度信息就丢了。这对后续的疲劳载荷分析影响很大。

# 重采样示例:降采样与升采样
import pandas as pd
import numpy as np

# 生成1秒间隔的风速数据(模拟10分钟)
time_1s = pd.date_range('2024-01-01 00:00:00', periods=600, freq='1s')
wind_1s = np.random.normal(6, 1.5, 600)  # 均值6m/s,标准差1.5
df_1s = pd.DataFrame({'风速': wind_1s}, index=time_1s)

# 降采样到1分钟:保留均值和标准差
df_1min = df_1s.resample('1min').agg({'风速': ['mean', 'std', 'min', 'max']})
print(df_1min.head())
# 输出:
#                         风速                  
#                        mean       std   min   max
# 2024-01-01 00:01:00  5.89  1.42  2.34  8.91
# 2024-01-01 00:02:00  6.12  1.51  2.78  9.23

# 升采样:从1分钟插值回1秒
df_1min_mean = df_1s.resample('1min').mean()
df_upsampled = df_1min_mean.resample('1s').interpolate(method='linear')
print(df_upsampled.head())
# 输出:
#                     风速
# 2024-01-01 00:01:00  5.89
# 2024-01-01 00:01:01  5.90  (线性插值)
# 2024-01-01 00:01:02  5.91

4.5 知识体系总览

下面这张图,把数据清洗与预处理的四个核心环节串起来了。你可以把它当作操作手册的目录。

数据清洗与预处理核心流程 原始数据 异常值检测 3σ / IQR / DBSCAN 缺失值填充 ffill / 插值 / 模型 时间戳对齐 UTC统一 / 时区处理 重采样 降采样 / 升采样 清洗后的高质量数据 数字孪生模型输入 每个环节都需结合风场实际工况,没有万能方案

数据清洗这件事,没有银弹。每个风场、每台风机、甚至每个传感器都有自己的“脾气”。我的经验是:先理解数据,再动手清洗。花30%的时间做数据探索,比花70%的时间调清洗参数要划算得多。

最后提醒一句: 清洗后的数据一定要做质量验证。我习惯用“清洗前后统计量对比”和“可视化检查”双重验证。别信脚本跑完就完事了,肉眼看一下曲线,很多问题一眼就能看出来。

公众号:蓝海资料掘金营,微信deep3321