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%就得警惕了。
填充方法怎么选?我一般按这个优先级来:
- 前向填充(ffill):用上一个有效值填充。适合短时间缺失,比如几秒钟的数据丢包。
- 线性插值:用前后两个有效值做线性拟合。适合变化平缓的变量,比如环境温度。
- 时间序列插值:考虑时间权重,用更复杂的插值方法(如样条插值)。适合风速这种波动较大的数据。
- 模型预测填充:用其他相关变量(如功率、桨距角)来预测缺失值。这是最后的手段,计算量大,但精度高。
我曾经接手过一个项目,运维团队用均值填充了连续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 知识体系总览
下面这张图,把数据清洗与预处理的四个核心环节串起来了。你可以把它当作操作手册的目录。
数据清洗这件事,没有银弹。每个风场、每台风机、甚至每个传感器都有自己的“脾气”。我的经验是:先理解数据,再动手清洗。花30%的时间做数据探索,比花70%的时间调清洗参数要划算得多。
公众号:蓝海资料掘金营,微信deep3321