4. 数据预处理与清洗:打好数字孪生的地基

各位工程师朋友,咱们接着聊。上一章我们把风场的数据源梳理清楚了,但说实话,从现场拿回来的数据,那叫一个「脏」。你想想看,传感器在野外风吹日晒,通讯偶尔断个线,采集系统时不时抽个风——这些数据直接拿来建模?那数字孪生出来的结果,你敢信吗?

我个人习惯,拿到原始数据后,第一件事不是急着分析,而是先做预处理与清洗。这一步花的时间,往往占整个项目的一半以上。别嫌烦,地基打不牢,楼盖得再高也得塌。

核心原则: 数据预处理的目标,是让数据「干净、连续、一致」。干净指剔除异常,连续指补全缺失,一致指统一时间与量纲。

4.1 缺失值处理:别让数据「断片」

风场数据里,缺失值太常见了。通讯故障、传感器维护、甚至鸟撞了设备,都会导致数据记录中断。我遇到过最夸张的一次,某台风机连续3天的风速数据全是NaN,后来发现是网线被老鼠咬断了。

处理缺失值,主要有两招:删除插值

4.1.1 直接删除

简单粗暴,但有效。如果缺失比例很小(比如低于5%),而且缺失是随机发生的,直接删掉整行数据,对整体统计影响不大。

# Python示例:删除含有NaN的行
import pandas as pd
df = pd.read_csv('wind_turbine_data.csv')
df_clean = df.dropna()  # 默认删除任何包含NaN的行
print(f"删除前:{len(df)}行,删除后:{len(df_clean)}行")
注意: 如果缺失数据集中在某个时间段(比如连续1小时全缺失),千万别直接删。那会破坏时间序列的连续性,后续做频谱分析或动态仿真时会出大问题。

4.1.2 插值填充

更优雅的做法是插值。说白了,就是用已知数据「猜」出缺失值。常用的方法有:

  • 线性插值: 假设缺失点前后数据呈直线变化。适合风速、功率这类变化平缓的参数。
  • 多项式插值: 用曲线拟合,适合非线性变化。但小心过拟合,我吃过这个亏。
  • 前向/后向填充: 用上一个或下一个有效值填充。适合短时间缺失,比如通讯中断几秒钟。
# 线性插值示例
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')

# 前向填充
df['wind_speed'] = df['wind_speed'].fillna(method='ffill')
我的经验: 对于风场数据,我一般先用线性插值处理短时间缺失(<10个采样点),超过这个范围,我会结合相邻风机的数据做相关性插值。比如A风机缺数据,但B风机在附近,风速相关性很高,可以用B的数据做参考。这招叫「空间插值」,后面讲多源数据融合时会细说。

4.2 异常值检测:揪出「捣乱分子」

数据里总有些「不速之客」。比如风速突然飙到100m/s,或者功率为负值——这明显不合理。异常值不处理,模型会被带偏。

我常用的检测方法有三种,各有各的适用场景。

4.2.1 3σ 法则(拉依达准则)

假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,视为异常。

import numpy as np
mean = df['wind_speed'].mean()
std = df['wind_speed'].std()
df_clean = df[(df['wind_speed'] >= mean - 3*std) & 
              (df['wind_speed'] <= mean + 3*std)]
注意: 3σ法则对极端值非常敏感。如果数据本身包含大量异常,均值和标准差会被污染,导致检测失效。我建议先用其他方法粗筛一遍,再用3σ精筛。

4.2.2 IQR(四分位距法)

不依赖正态分布假设,更稳健。IQR = Q3 - Q1,通常把小于Q1-1.5×IQR或大于Q3+1.5×IQR的数据视为异常。

Q1 = df['wind_speed'].quantile(0.25)
Q3 = df['wind_speed'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
df_clean = df[(df['wind_speed'] >= lower_bound) & 
              (df['wind_speed'] <= upper_bound)]

我个人更偏爱IQR,因为它对数据分布不敏感。风场数据往往不是完美的正态分布,用IQR更靠谱。

4.2.3 DBSCAN(基于密度的聚类)

这是进阶玩法。DBSCAN不依赖统计假设,而是根据数据点的密度来识别异常。密度低的点,就是异常点。

from sklearn.cluster import DBSCAN
# 假设我们有两维特征:风速和功率
X = df[['wind_speed', 'power']].values
clustering = DBSCAN(eps=3, min_samples=10).fit(X)
# 标签为-1的点就是异常点
df_clean = df[clustering.labels_ != -1]
避坑指南: 我曾经用DBSCAN处理一个风场数据,结果把正常的高风速段全标成了异常。后来发现是参数eps(邻域半径)设得太小了。记住,DBSCAN对参数敏感,建议先用可视化工具(比如散点图)观察数据分布,再调参。

4.3 时间戳对齐与重采样:让数据「步调一致」

风场里不同传感器的采样频率可能不一样。风速仪可能每秒采一次,功率表每5秒采一次,温度传感器每10秒采一次。时间戳不对齐,后续分析根本没法做。

我的做法是:统一时间基准,然后重采样

4.3.1 时间戳对齐

先把所有数据的时间戳转换成统一的时区(比如UTC),然后按相同的时间间隔排列。

# 确保时间戳是datetime类型
df['timestamp'] = pd.to_datetime(df['timestamp'], utc=True)
# 设置为索引
df = df.set_index('timestamp')

4.3.2 重采样

重采样就是把数据从一种频率转换到另一种频率。比如把1秒的数据降采样到10秒,或者把10秒的数据升采样到1秒。

# 降采样:从1秒到10秒,取平均值
df_resampled = df.resample('10S').mean()

# 升采样:从10秒到1秒,用插值填充
df_resampled = df.resample('1S').interpolate()
关键点: 降采样时,注意选择合适的聚合函数(均值、中位数、最大值)。比如风速用均值,但阵风分析时可能需要最大值。升采样时,插值方法要合理,别引入虚假信息。

4.4 数据标准化与归一化:让数据「平起平坐」

风速、功率、温度、振动……这些参数的量纲和数值范围完全不同。风速可能是0-30m/s,功率是0-2000kW,温度是-20-50℃。如果不做标准化,数值大的特征会主导模型,数值小的特征就被淹没了。

常用的方法有两种:

4.4.1 归一化(Min-Max Scaling)

把数据缩放到[0,1]区间。公式很简单:X' = (X - X_min) / (X_max - X_min)

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)

4.4.2 标准化(Z-score Scaling)

把数据转换成均值为0、标准差为1的分布。公式:X' = (X - μ) / σ

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)
我的选择: 如果后续要用神经网络或深度学习,我倾向于用归一化,因为输出范围固定,训练更稳定。如果要用线性回归或SVM,标准化更合适,因为它保留了数据的分布形状。记住,标准化不改变数据的分布形态,只是改变了尺度和位置。

知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作数据预处理的「作战地图」。

数据预处理与清洗核心流程 原始数据 缺失值处理 删除 插值 空间插值 异常值检测 3σ法则 IQR法 DBSCAN 可视化检查 干净、连续、一致的数据

数据预处理没有银弹。每个风场、每台风机都有自己的「脾气」。我建议你建立一套标准流程,但也要保留灵活调整的空间。比如,海上风机的数据噪声通常比陆上大,异常检测的阈值就要适当放宽。

好了,数据洗干净了,下一章我们就可以开始真正的建模了。记住,预处理花的时间,会在后续建模和仿真中加倍回报给你。


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