数据清洗实战:缺失值处理、异常值检测、数据重采样
各位同学,咱们今天聊点实在的。
上一章我们把SCADA数据从现场拽回来了,但说实话,那些原始数据根本没法直接用。我见过太多新手,拿到数据就急着建模,结果模型跑出来一塌糊涂。为什么?因为数据太脏了。
数据清洗,说白了就是给数据“洗澡”。把脏东西洗掉,把缺失的补上,把异常的揪出来。今天我就带大家走一遍这个流程。
核心观点:数据清洗不是体力活,而是技术活。清洗质量直接决定后续分析的成败。
一、缺失值处理:别让“空”坑了你
风机的SCADA系统,经常因为传感器故障、通讯中断、维护停机等原因,产生缺失值。我遇到过一台风机,风速传感器坏了三天,那三天的数据全是NaN。你想想看,如果直接拿这些数据去训练模型,模型会学到什么?
处理缺失值,我一般分三步走:
- 先诊断:看看缺失率有多高,缺失是随机的还是有规律的
- 再决策:根据缺失率选择处理策略
- 后执行:用合适的方法填充或删除
具体来说,我常用的方法有这些:
| 缺失率 | 推荐方法 | 适用场景 |
|---|---|---|
| < 5% | 前向填充 / 线性插值 | 风速、功率等连续变量 |
| 5% - 20% | KNN填充 / 多重插补 | 温度、压力等关联性强的变量 |
| > 20% | 直接删除该特征 | 缺失太多,填充反而引入噪声 |
我的小技巧:对于风速、功率这种有物理规律的变量,我个人习惯用线性插值。因为风机运行是连续的,前一秒和后一秒的值不会突变。但如果是振动信号这种高频数据,我建议用前向填充,更稳妥。
来看一段代码,这是我在项目里常用的缺失值处理流程:
import pandas as pd
import numpy as np
# 读取数据
df = pd.read_csv('wind_turbine_data.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)
# 第一步:检查缺失率
missing_rate = df.isnull().sum() / len(df) * 100
print("各特征缺失率:\n", missing_rate)
# 第二步:按缺失率处理
for col in df.columns:
rate = missing_rate[col]
if rate < 5:
# 低缺失率,用线性插值
df[col] = df[col].interpolate(method='linear')
elif rate < 20:
# 中等缺失率,用前向填充
df[col] = df[col].fillna(method='ffill')
else:
# 高缺失率,直接删除
df.drop(columns=[col], inplace=True)
print(f"删除特征:{col},缺失率 {rate:.1f}%")
print("缺失值处理完成,剩余特征数:", df.shape[1])
注意:千万不要用均值填充!我曾经在一个项目里用均值填充风速数据,结果模型预测的功率曲线完全失真。因为风速的分布不是对称的,均值填充会破坏原始分布特征。
二、异常值检测:揪出那些“捣乱分子”
异常值,就是那些明显偏离正常范围的数据点。比如风速显示200m/s,或者功率为负值。这些数据要么是传感器故障,要么是通讯错误,必须处理掉。
我常用的两种方法:3σ原则和IQR方法。这两种方法各有千秋,我给大家拆开讲。
2.1 3σ原则:适合正态分布的数据
3σ原则,说白了就是:如果数据服从正态分布,那么99.7%的数据会落在均值±3个标准差之内。超出这个范围的就是异常值。
这个方法适合风速、温度这种近似正态分布的变量。但要注意,功率数据通常不是正态分布,用3σ原则会误删很多正常的高功率点。
def detect_outliers_3sigma(df, column):
mean = df[column].mean()
std = df[column].std()
lower = mean - 3 * std
upper = mean + 3 * std
outliers = df[(df[column] < lower) | (df[column] > upper)]
return outliers, lower, upper
# 以风速为例
outliers, low, up = detect_outliers_3sigma(df, 'wind_speed')
print(f"风速异常值数量:{len(outliers)}")
print(f"正常范围:[{low:.2f}, {up:.2f}]")
2.2 IQR方法:更稳健的选择
IQR方法不依赖正态分布假设,它基于四分位数。具体来说:
- 计算Q1(25%分位数)和Q3(75%分位数)
- IQR = Q3 - Q1
- 正常范围:[Q1 - 1.5*IQR, Q3 + 1.5*IQR]
我个人更偏爱IQR方法。为什么?因为风机的很多数据都不服从正态分布。比如振动信号,它的分布往往是偏态的。用3σ原则会误判,但IQR方法就稳健得多。
def detect_outliers_iqr(df, column):
Q1 = df[column].quantile(0.25)
Q3 = df[column].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df[column] < lower) | (df[column] > upper)]
return outliers, lower, upper
# 以有功功率为例
outliers, low, up = detect_outliers_iqr(df, 'active_power')
print(f"功率异常值数量:{len(outliers)}")
print(f"正常范围:[{low:.2f}, {up:.2f}]")
避坑指南:我曾经遇到过一个案例,用IQR方法检测齿轮箱温度,发现大量异常值。后来一查,原来是夏天高温天气导致的正常温度升高。所以,异常值检测一定要结合业务场景,不能一刀切。
三、数据重采样:统一时间尺度
风机的SCADA数据,采样频率往往不一致。有的传感器每10秒采一次,有的每1分钟采一次。如果不做重采样,后续分析根本没法对齐。
重采样,就是把不同频率的数据统一到同一个时间尺度上。我常用的重采样策略:
| 原始频率 | 目标频率 | 聚合方法 | 适用场景 |
|---|---|---|---|
| 10秒 | 1分钟 | 均值 | 风速、功率 |
| 1分钟 | 10分钟 | 均值 | 寿命评估 |
| 1小时 | 1天 | 均值+极值 | 长期趋势分析 |
为什么我建议用10分钟均值?因为风机的动态响应时间大约在几分钟级别。10分钟均值既能平滑掉短时波动,又能保留运行特征。这是我多年项目经验总结出来的。
# 重采样到10分钟
df_resampled = df.resample('10T').agg({
'wind_speed': 'mean',
'active_power': 'mean',
'rotor_speed': 'mean',
'generator_temp': 'max', # 温度取最大值,关注极端情况
'blade_angle': 'mean'
})
# 检查重采样后的数据
print("重采样前数据量:", len(df))
print("重采样后数据量:", len(df_resampled))
print("时间范围:", df_resampled.index.min(), "至", df_resampled.index.max())
注意:重采样时,聚合函数的选择很重要。对于温度、振动这类关注极值的变量,我建议用max或min,而不是均值。因为均值会掩盖极端情况,而极端情况往往是故障的前兆。
四、本章知识体系
下面这张图,是我梳理的数据清洗整体流程。大家可以对照着看,心里有个谱。
这张图把今天的核心内容串起来了。从原始数据出发,经过缺失值处理、异常值检测、数据重采样三个步骤,最终得到高质量数据,才能用于寿命评估模型。
总结一下:数据清洗不是一次性工作,而是需要反复迭代的过程。我建议每次清洗后都做一次可视化检查,看看数据分布是否合理。如果发现异常,回头再调整参数。这才是数据科学家的正确姿势。
好了,今天的内容就到这里。代码我已经贴出来了,大家回去跑一跑,看看自己的数据能洗出什么结果。有问题随时交流。