数据清洗实战:缺失值处理、异常值检测、数据重采样

各位同学,咱们今天聊点实在的。

上一章我们把SCADA数据从现场拽回来了,但说实话,那些原始数据根本没法直接用。我见过太多新手,拿到数据就急着建模,结果模型跑出来一塌糊涂。为什么?因为数据太脏了。

数据清洗,说白了就是给数据“洗澡”。把脏东西洗掉,把缺失的补上,把异常的揪出来。今天我就带大家走一遍这个流程。

核心观点:数据清洗不是体力活,而是技术活。清洗质量直接决定后续分析的成败。

一、缺失值处理:别让“空”坑了你

风机的SCADA系统,经常因为传感器故障、通讯中断、维护停机等原因,产生缺失值。我遇到过一台风机,风速传感器坏了三天,那三天的数据全是NaN。你想想看,如果直接拿这些数据去训练模型,模型会学到什么?

处理缺失值,我一般分三步走:

  1. 先诊断:看看缺失率有多高,缺失是随机的还是有规律的
  2. 再决策:根据缺失率选择处理策略
  3. 后执行:用合适的方法填充或删除

具体来说,我常用的方法有这些:

缺失率 推荐方法 适用场景
< 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,而不是均值。因为均值会掩盖极端情况,而极端情况往往是故障的前兆。

四、本章知识体系

下面这张图,是我梳理的数据清洗整体流程。大家可以对照着看,心里有个谱。

数据清洗实战知识体系 原始SCADA数据 缺失值处理 异常值检测 数据重采样 处理方法 • 前向填充(ffill) • 线性插值 / KNN填充 检测方法 • 3σ原则(正态分布) • IQR方法(稳健) 聚合策略 • 均值(平滑波动) • 极值(关注极端) 清洗后的高质量数据 寿命评估模型

这张图把今天的核心内容串起来了。从原始数据出发,经过缺失值处理、异常值检测、数据重采样三个步骤,最终得到高质量数据,才能用于寿命评估模型。

总结一下:数据清洗不是一次性工作,而是需要反复迭代的过程。我建议每次清洗后都做一次可视化检查,看看数据分布是否合理。如果发现异常,回头再调整参数。这才是数据科学家的正确姿势。

好了,今天的内容就到这里。代码我已经贴出来了,大家回去跑一跑,看看自己的数据能洗出什么结果。有问题随时交流。

蓝海数据掘金营,专注资料整理