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

数据预处理,说白了就是给模型「喂干净饭」。

我见过太多新手,模型调了半天效果上不去,最后发现是原始数据里一堆坑。嗯,今天咱们就把这些坑填平。

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

风电数据里,缺失值太常见了。传感器故障、通信中断、维护停机……原因五花八门。我个人习惯,拿到数据第一件事就是检查缺失率。

核心原则:缺失率低于5%,直接删除或简单填充;超过20%,必须用插值法;超过50%,建议重新审视数据采集方案。

1. 前向填充(Forward Fill)

最简单粗暴的方法。用上一个有效值填充后面的缺失值。

为什么能用?风电数据是时间序列,相邻时刻的风速、功率变化不会太大。我曾在某风场项目中,用前向填充处理了连续3小时的缺失数据,效果居然还不错。

import pandas as pd

# 前向填充
df['wind_speed'].fillna(method='ffill', inplace=True)

# 限制填充步数,最多填充5个连续缺失
df['wind_speed'].fillna(method='ffill', limit=5, inplace=True)

注意:如果缺失值出现在数据开头,前向填充会失效。我曾经踩过这个坑,后来改用后向填充先处理开头部分。

2. 插值法(Interpolation)

插值法更精细。它利用缺失点前后的数据,拟合出一条曲线来估算缺失值。

# 线性插值
df['wind_speed'].interpolate(method='linear', inplace=True)

# 时间插值(考虑时间间隔)
df['wind_speed'].interpolate(method='time', inplace=True)

# 多项式插值(阶数不宜过高)
df['wind_speed'].interpolate(method='polynomial', order=2, inplace=True)

我个人偏好时间插值。为什么?因为风电数据的时间戳往往不均匀,时间插值能更准确地反映真实变化趋势。

避坑指南:我曾经用高阶多项式插值处理一段剧烈波动的风速数据,结果插出了负值。嗯,物理上不可能。后来我改用线性插值加限幅处理,才解决了问题。

二、异常值检测与修正:揪出那些「离谱」的数据

异常值比缺失值更隐蔽。它看起来像正常数据,但实际是错的。比如风速突然飙到100m/s,或者功率超过额定值两倍——这些明显不合理。

1. 3-Sigma 法则

假设数据服从正态分布,超出均值±3倍标准差的值视为异常。

import numpy as np

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.index.tolist()

# 使用
outlier_idx = detect_outliers_3sigma(df, 'wind_speed')

但要注意,风电数据往往不是严格的正态分布。我遇到过风速数据呈双峰分布的情况,3-Sigma 会把很多正常值误判为异常。

2. IQR 方法(四分位距法)

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.index.tolist()

# 使用
outlier_idx = detect_outliers_iqr(df, 'power')

我的经验:对于风电功率数据,IQR 比 3-Sigma 更靠谱。因为功率数据往往有上下限(0到额定功率),IQR 能更好地适应这种有界分布。

3. 异常值修正

检测到异常值后,别急着删除。我建议先看看能不能修正。

# 方法1:用中位数替换
df.loc[outlier_idx, 'wind_speed'] = df['wind_speed'].median()

# 方法2:用前后均值替换
for idx in outlier_idx:
    if idx > 0 and idx < len(df)-1:
        df.loc[idx, 'wind_speed'] = (df.loc[idx-1, 'wind_speed'] + df.loc[idx+1, 'wind_speed']) / 2

# 方法3:用插值法修正
df['wind_speed'] = df['wind_speed'].interpolate(method='linear')

我个人习惯用插值法修正异常值。为什么?因为它能保持数据的时序连续性,不会像中位数替换那样引入突兀的跳变。

曾经踩过的坑:有一次我批量删除了所有异常值,结果模型训练时因为样本量骤减,性能反而下降了。后来我改成先修正、再删除的策略——能修正的尽量修正,实在离谱的才删。

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

风速、温度、湿度、功率……这些特征的量纲完全不同。风速是m/s,温度是℃,功率是kW。如果不做标准化,模型会天然偏向数值大的特征。

1. 标准化(Z-score Normalization)

将数据转换为均值为0、标准差为1的分布。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = pd.DataFrame(
    scaler.fit_transform(df[['wind_speed', 'power', 'temperature']]),
    columns=['wind_speed', 'power', 'temperature']
)

标准化适合数据近似正态分布的场景。我一般在做线性模型或神经网络时用标准化。

2. 归一化(Min-Max Scaling)

将数据缩放到[0, 1]区间。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
df_normalized = pd.DataFrame(
    scaler.fit_transform(df[['wind_speed', 'power', 'temperature']]),
    columns=['wind_speed', 'power', 'temperature']
)

归一化适合数据有明确上下界的场景。比如功率值,天然在0到额定功率之间,用归一化就很自然。

我的建议:如果你不确定用哪个,先试试标准化。它在大多数场景下表现更稳定。归一化对异常值敏感,如果数据里有离群点,归一化会把正常值压缩到很小的区间。

3. 什么时候不做标准化?

你可能会问:是不是所有模型都需要标准化?

不是。树模型(随机森林、XGBoost、LightGBM)对特征尺度不敏感,标准化反而没用。我刚开始做风电预测时,给XGBoost做了标准化,结果性能一点没变——白费功夫。

总结一下:

  • 线性模型、神经网络、SVM → 必须标准化/归一化
  • 树模型 → 不需要
  • KNN、聚类 → 建议做,否则距离计算会出问题

四、知识体系总览

下面这张图,把本章的核心逻辑串起来了。你想想看,数据预处理其实就三步:填坑(缺失值)、排雷(异常值)、统一尺度(标准化)。

数据预处理知识体系 原始风电数据 缺失值处理 异常值检测与修正 标准化与归一化 方法 • 前向填充(ffill) • 线性插值 • 时间插值 方法 • 3-Sigma 法则 • IQR 四分位距法 • 中位数/插值修正 方法 • Z-score 标准化 • Min-Max 归一化 • 树模型可跳过 干净可用的数据

嗯,数据预处理这块,说难不难,说简单也不简单。关键是要理解每个方法背后的假设和适用场景。我在项目中见过太多人盲目套用方法,结果适得其反。

记住一句话:没有最好的方法,只有最合适的方法。多试、多对比、多思考,你也能成为数据预处理的高手。

专注资料整理