4. 数据清洗与预处理:打好建模的基础
大家好,我是老张。做风电运维算法这些年,我最大的体会就是:数据清洗花的时间,往往比建模本身还多。你想想看,风机主轴承温度数据,从SCADA系统采集过来,中间经过传输、存储,难免会有各种“脏”数据。如果这一步没做好,后面模型再花哨也是白搭。
今天我们就来聊聊数据清洗与预处理的几个核心环节。我个人习惯把这部分叫做“数据炼油”——把原油提炼成可用的汽油,才能驱动模型这辆跑车。
4.1 缺失值处理策略
先说缺失值。风机数据里,缺失值太常见了——传感器故障、通信中断、存储异常,都可能导致数据点丢失。我记得有一次,某风场连续3天的数据全是NaN,后来发现是交换机端口松了。
处理缺失值,我常用的方法有两种:前向填充和插值。
4.1.1 前向填充(Forward Fill)
前向填充,说白了就是用上一个有效值填充当前缺失值。对于主轴承温度这种缓慢变化的物理量,这个方法很实用。你想想看,温度不会突然跳变,前一秒的温度和后一秒的差不了多少。
适用场景:短时间缺失(比如1-2个采样点),且数据变化平缓。
import pandas as pd
import numpy as np
# 模拟主轴承温度数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=100, freq='10min')
temp = 70 + np.random.randn(100) * 2
temp[10:15] = np.nan # 人为制造缺失
df = pd.DataFrame({'timestamp': dates, 'bearing_temp': temp})
df.set_index('timestamp', inplace=True)
# 前向填充
df['temp_ffill'] = df['bearing_temp'].fillna(method='ffill')
print(df.head(20))
我的小技巧:前向填充后,最好检查一下连续缺失的长度。如果连续缺失超过5个点,我建议改用插值法,否则误差会累积。
4.1.2 插值法(Interpolation)
插值法更灵活一些。它利用缺失点前后的数据,通过数学方法估算缺失值。Pandas里提供了多种插值方式,我最常用的是linear和time。
# 线性插值
df['temp_interp'] = df['bearing_temp'].interpolate(method='linear')
# 时间插值(考虑时间间隔)
df['temp_time'] = df['bearing_temp'].interpolate(method='time')
print(df[['bearing_temp', 'temp_ffill', 'temp_interp']].head(20))
为什么推荐time插值?因为风机数据的时间戳往往不是严格等间隔的。比如某条数据晚了2分钟,用time方法会考虑这个时间差,结果更准确。我在项目中遇到过,用linear和time插值,最终模型精度差了0.5%。
注意:如果缺失值出现在数据开头或结尾,插值法会失效。这时候我一般用前向填充或后向填充兜底。
4.2 异常值识别与处理
异常值,就是那些“离谱”的数据点。比如主轴承温度突然飙到150°C,或者掉到-20°C。这些数据要么是传感器故障,要么是通信错误,必须处理掉。
识别异常值,我常用两种方法:3-Sigma和IQR。
4.2.1 3-Sigma 法则
3-Sigma基于正态分布假设。数据落在均值±3倍标准差之外的概率只有0.3%,所以这些点可以视为异常。
def detect_outliers_3sigma(data, col):
mean = data[col].mean()
std = data[col].std()
lower = mean - 3 * std
upper = mean + 3 * std
outliers = data[(data[col] < lower) | (data[col] > upper)]
return outliers, lower, upper
outliers_3s, low, up = detect_outliers_3sigma(df, 'bearing_temp')
print(f"3-Sigma 下界: {low:.2f}, 上界: {up:.2f}")
print(f"检测到 {len(outliers_3s)} 个异常值")
经验之谈:3-Sigma对极端值敏感。如果数据本身有多个极端值,均值和标准差会被“带偏”,导致漏检。这时候我会先做一次IQR粗筛,再用3-Sigma精筛。
4.2.2 IQR 方法
IQR(四分位距)不受极端值影响,更稳健。它的原理是:计算Q1(25%分位数)和Q3(75%分位数),IQR = Q3 - Q1。异常值定义为小于Q1 - 1.5*IQR或大于Q3 + 1.5*IQR的点。
def detect_outliers_iqr(data, col):
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = data[(data[col] < lower) | (data[col] > upper)]
return outliers, lower, upper
outliers_iqr, low_iqr, up_iqr = detect_outliers_iqr(df, 'bearing_temp')
print(f"IQR 下界: {low_iqr:.2f}, 上界: {up_iqr:.2f}")
print(f"检测到 {len(outliers_iqr)} 个异常值")
处理异常值,我一般直接删除。但要注意,删除前先确认是不是真的异常。我曾经遇到一个案例,某风场在低温天气下,主轴承温度数据频繁被标记为异常,后来发现是温度传感器安装位置有问题,不是数据本身的问题。
避坑指南:删除异常值后,记得检查数据量是否足够。如果删除了超过5%的数据,建议重新审视阈值设置。
4.3 时间戳对齐与重采样
最后一步,时间戳对齐。风机数据来自多个传感器,每个传感器的采样频率可能不同。比如主轴承温度每10分钟采集一次,而振动数据每5分钟采集一次。如果不做对齐,模型就没法用。
重采样(Resampling)是解决这个问题的标准方法。我把所有数据统一到同一个时间频率上,比如10分钟。
# 生成模拟的多频率数据
dates_10min = pd.date_range('2024-01-01', periods=50, freq='10min')
dates_5min = pd.date_range('2024-01-01', periods=100, freq='5min')
temp_data = pd.DataFrame({'timestamp': dates_10min, 'temp': 70 + np.random.randn(50) * 2})
vib_data = pd.DataFrame({'timestamp': dates_5min, 'vibration': 0.5 + np.random.randn(100) * 0.1})
# 设置索引
temp_data.set_index('timestamp', inplace=True)
vib_data.set_index('timestamp', inplace=True)
# 重采样到10分钟,取平均值
vib_resampled = vib_data.resample('10min').mean()
# 合并数据
aligned_data = temp_data.join(vib_resampled, how='outer')
print(aligned_data.head(10))
重采样时,聚合方式要选对。对于温度这种连续量,我一般用均值;对于振动这种波动大的量,也可以用最大值或均方根。具体选哪个,取决于你的业务需求。
注意:重采样后,如果某个时间窗口内没有原始数据,会产生新的缺失值。这时候需要回到4.1节,再做一次缺失值处理。
小结
数据清洗与预处理,说白了就是“把脏数据洗干净”。缺失值用前向填充或插值,异常值用3-Sigma或IQR,时间戳用重采样对齐。这三步走完,数据基本就“能用”了。
我个人习惯把清洗后的数据保存一份副本,方便后续回溯。你想想看,万一模型效果不好,至少能确认不是数据的问题。
好了,这一章就到这里。下一章我们聊聊特征工程,看看怎么从清洗后的数据里挖出更多有价值的信息。