3、数据预处理实战:缺失值处理、异常值检测与修正、数据标准化与归一化

大家好,我是老张。做风电AI运维这些年,我踩过最大的坑,就是数据预处理没做好。

你想想看,风机SCADA系统一天能产生上千万条数据。但这些数据里,有传感器故障导致的缺失值,有通信中断产生的异常跳变,还有不同量纲带来的计算偏差。说白了,垃圾数据进,垃圾模型出。今天这一章,我就把压箱底的数据预处理经验全盘托出。

核心观点:数据预处理占整个异常检测项目60%以上的工作量。预处理做得好,模型训练事半功倍。

数据预处理实战知识体系 数据预处理 缺失值处理 • 删除法(行/列删除) • 均值/中位数填充 • 前向/后向填充 • 插值法(线性/样条) 异常值检测与修正 • 3σ原则(正态分布) • IQR四分位距法 • 箱线图可视化 • 修正:截尾/替换/插值 标准化与归一化 • Z-score标准化 • Min-Max归一化 • Robust标准化 • 风电场景选择策略

3.1 缺失值处理:别让空值毁了你的模型

我在项目中遇到过最头疼的事——某风场连续3天的风速数据全部为0。一开始以为是没风,后来发现是传感器冻住了。这就是典型的缺失值问题。

风电数据缺失的原因很多:传感器故障、通信中断、数据存储异常。我个人习惯,拿到数据第一件事就是检查缺失率。

import pandas as pd
import numpy as np

# 加载风电数据
df = pd.read_csv('wind_turbine_data.csv')

# 检查缺失值概览
print("各列缺失数量:")
print(df.isnull().sum())

# 计算缺失率
missing_rate = df.isnull().sum() / len(df) * 100
print("\n缺失率(%):")
print(missing_rate)

嗯,这里要注意。缺失率低于5%的列,我一般直接删除缺失行。但缺失率超过30%的,就得慎重了。

我的经验:风速、功率这类关键参数,缺失率超过10%就别删了。用插值法补全,效果比删除好得多。

常用的缺失值处理方法,我整理了一张表:

方法 适用场景 代码示例 注意事项
删除法 缺失率<5%,随机缺失 df.dropna() 会损失样本量
均值填充 数据分布对称,无极端值 df.fillna(df.mean()) 会降低方差
中位数填充 数据有异常值或偏态分布 df.fillna(df.median()) 稳健性更好
前向填充 时间序列数据,短时间缺失 df.fillna(method='ffill') 适合连续变化参数
线性插值 风速、温度等连续变量 df.interpolate() 我最推荐的方法
# 实战:综合处理缺失值
# 1. 删除缺失率超过50%的列
df = df.dropna(thresh=len(df)*0.5, axis=1)

# 2. 风速列用线性插值
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')

# 3. 温度列用前向填充(温度变化缓慢)
df['temperature'] = df['temperature'].fillna(method='ffill')

# 4. 功率列用中位数填充(有异常值风险)
df['active_power'] = df['active_power'].fillna(df['active_power'].median())

3.2 异常值检测与修正:揪出那些捣乱的数据点

为什么异常值这么要命?我举个例子。有一次做功率预测模型,训练集里混入了一个风速80m/s、功率2000kW的异常点。结果模型学歪了,预测值整体偏高。后来排查才发现,是SCADA系统某次数据采集时发生了位翻转。

异常值检测,我常用两种方法:

方法一:3σ原则

说白了,就是假设数据服从正态分布。超出均值±3倍标准差的数据,视为异常。这个方法简单粗暴,但很有效。

def detect_outliers_3sigma(data, column):
    mean = data[column].mean()
    std = data[column].std()
    lower_bound = mean - 3 * std
    upper_bound = mean + 3 * std
    
    outliers = data[(data[column] < lower_bound) | 
                    (data[column] > upper_bound)]
    return outliers, lower_bound, upper_bound

# 检测风速异常值
outliers, low, high = detect_outliers_3sigma(df, 'wind_speed')
print(f"风速正常范围:{low:.2f} ~ {high:.2f} m/s")
print(f"检测到异常值数量:{len(outliers)}")

方法二:IQR四分位距法

这个方法更稳健,不依赖正态分布假设。用Q1和Q3的差值来判断。

def detect_outliers_iqr(data, column):
    Q1 = data[column].quantile(0.25)
    Q3 = data[column].quantile(0.75)
    IQR = Q3 - Q1
    
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    outliers = data[(data[column] < lower_bound) | 
                    (data[column] > upper_bound)]
    return outliers, lower_bound, upper_bound

# 检测功率异常值
outliers_power, low_p, high_p = detect_outliers_iqr(df, 'active_power')
print(f"功率正常范围:{low_p:.2f} ~ {high_p:.2f} kW")
print(f"检测到异常值数量:{len(outliers_power)}")

避坑指南:我曾经在某个风场直接用3σ原则检测风速异常,结果把正常的大风天气数据全标记为异常了。为什么?因为风速分布是偏态的,不是正态分布。后来改用IQR法,效果就好多了。

检测到异常值后,怎么修正?我一般分三步走:

  1. 确认异常原因:是传感器故障?还是真实极端工况?
  2. 截尾处理:将异常值替换为边界值(上下限)
  3. 插值修正:对于孤立异常点,用前后正常值插值替换
# 异常值修正实战
def correct_outliers(data, column, method='clip'):
    if method == 'clip':
        # 截尾处理:替换为边界值
        Q1 = data[column].quantile(0.25)
        Q3 = data[column].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR
        data[column] = data[column].clip(lower, upper)
    
    elif method == 'interpolate':
        # 插值修正:将异常值设为NaN,再插值
        Q1 = data[column].quantile(0.25)
        Q3 = data[column].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR
        mask = (data[column] < lower) | (data[column] > upper)
        data.loc[mask, column] = np.nan
        data[column] = data[column].interpolate()
    
    return data

# 对功率列进行截尾修正
df = correct_outliers(df, 'active_power', method='clip')

3.3 数据标准化与归一化:让所有特征站在同一起跑线

你想想看,风速是0-30m/s,功率是0-2000kW,温度是-20-50℃。这些数据量纲完全不同。如果不做标准化,模型会天然偏向数值大的特征。说白了,功率的2000和风速的20,在模型眼里差距巨大。

我常用的三种方法:

方法 公式 输出范围 适用场景
Z-score标准化 (x - μ) / σ 无固定范围,均值为0 数据近似正态分布,有异常值
Min-Max归一化 (x - min) / (max - min) [0, 1] 数据有明确边界,无极端值
Robust标准化 (x - median) / IQR 无固定范围 数据有较多异常值
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# 1. Z-score标准化(我最常用)
scaler_std = StandardScaler()
df['wind_speed_std'] = scaler_std.fit_transform(df[['wind_speed']])
df['active_power_std'] = scaler_std.fit_transform(df[['active_power']])

# 2. Min-Max归一化(适合神经网络输入)
scaler_mm = MinMaxScaler()
df['temperature_norm'] = scaler_mm.fit_transform(df[['temperature']])

# 3. Robust标准化(数据有异常值时用)
scaler_robust = RobustScaler()
df['wind_direction_robust'] = scaler_robust.fit_transform(df[['wind_direction']])

我的建议:做风电异常检测,我一般首选Z-score标准化。原因有二:一是风电数据常有异常值,Z-score对异常值不敏感;二是很多异常检测算法(如孤立森林、LOF)都假设数据经过Z-score处理。

最后,给新手一个完整的预处理流水线:

def wind_data_preprocessing(df):
    """风电数据预处理完整流水线"""
    # Step 1: 缺失值处理
    # 删除缺失率高的列
    df = df.dropna(thresh=len(df)*0.6, axis=1)
    # 关键参数插值
    for col in ['wind_speed', 'active_power', 'temperature']:
        if col in df.columns:
            df[col] = df[col].interpolate(method='linear')
    
    # Step 2: 异常值检测与修正
    for col in ['wind_speed', 'active_power']:
        if col in df.columns:
            df = correct_outliers(df, col, method='clip')
    
    # Step 3: 标准化
    scaler = StandardScaler()
    feature_cols = ['wind_speed', 'active_power', 'temperature', 
                    'wind_direction', 'rotor_speed']
    available_cols = [c for c in feature_cols if c in df.columns]
    df[available_cols] = scaler.fit_transform(df[available_cols])
    
    return df, scaler

# 执行预处理
df_clean, scaler = wind_data_preprocessing(df)
print("预处理完成!数据形状:", df_clean.shape)

好了,数据预处理这块就讲到这里。记住一句话:预处理花的时间,都会在模型效果上还回来。别嫌麻烦,每一步都值得认真对待。


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