第三章 数据清洗实战:异常值检测与缺失值处理

各位同行,欢迎来到数据清洗这一章。说实话,我在风电场干了这么多年,最头疼的往往不是算法选型,而是数据本身的质量问题。你想想看,一台风机每天产生几万条数据,里面混着传感器漂移、通信中断、极端工况……如果不把这些「脏数据」处理干净,后面再漂亮的模型也是白搭。

今天咱们就聊聊数据清洗的三个核心动作:异常值检测、缺失值处理、数据标准化。我会结合自己踩过的坑,把实战经验掰开揉碎了讲。

3.1 异常值检测:别让坏数据带偏了方向

异常值检测,说白了就是找出那些「不合理」的数据点。比如风速突然跳到50m/s,或者功率曲线完全偏离理论范围。我个人习惯先做可视化,看一眼数据分布,心里有个底。

3.1.1 阈值法:简单粗暴但有效

阈值法是最直接的方法。根据物理常识或行业标准,设定上下限。比如风速一般不会超过25m/s,功率不会超过额定值的1.2倍。

# 阈值法示例(Python)
import pandas as pd
import numpy as np

# 假设df是风电场数据,包含'风速'和'功率'两列
df = pd.read_csv('wind_farm_data.csv')

# 设定物理阈值
风速上限 = 25  # m/s
功率上限 = 1.2 * 1500  # 额定功率1500kW,留20%余量

# 标记异常
异常风速 = df['风速'] > 风速上限
异常功率 = df['功率'] > 功率上限

# 处理:删除或替换
df_clean = df[~(异常风速 | 异常功率)]
print(f"删除了 {len(df) - len(df_clean)} 条异常数据")
我的经验:阈值法适合处理明显超出物理范围的数据。但要注意,不同机型的额定参数不同,别一刀切。我曾经遇到过某台老旧风机,实际出力只有铭牌值的80%,用统一阈值反而误删了正常数据。

3.1.2 四分位法:更智能的统计方法

阈值法依赖人工经验,而四分位法(IQR)完全基于数据本身的分布。它把数据分成四等份,取Q1(25%分位)和Q3(75%分位),然后定义异常值为超出 [Q1 - 1.5*IQR, Q3 + 1.5*IQR] 范围的数据。

# 四分位法示例
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[column] < lower_bound) | (data[column] > upper_bound)
    return outliers

# 对功率列检测异常
功率异常 = detect_outliers_iqr(df, '功率')
df_clean = df[~功率异常]
注意:四分位法对偏态分布的数据不太友好。比如风速数据通常右偏(低风速多,高风速少),用1.5倍IQR可能会把正常的高风速点误判为异常。我建议先看数据分布,再决定是否调整倍数(比如用3倍IQR)。

3.2 缺失值处理:补还是不补?这是个问题

数据缺失在风电场太常见了。传感器故障、通信中断、维护停机……都会导致数据断档。处理缺失值,我一般分三步走:先看缺失比例,再看缺失模式,最后选方法。

3.2.1 删除法:简单但代价高

如果某列缺失超过70%,或者某行缺失的关键字段太多,直接删除是最省事的。但要注意,删除会损失信息,尤其是时间序列数据,删掉一行可能破坏连续性。

# 删除缺失比例过高的列
threshold = 0.7  # 70%缺失就删除
df_clean = df.dropna(thresh=len(df) * (1 - threshold), axis=1)

# 删除缺失关键字段的行
df_clean = df_clean.dropna(subset=['风速', '功率'])
我的建议:删除法适合「缺失比例高」或「缺失随机且样本量充足」的情况。如果数据量本来就少,比如只有一个月的数据,删除太多会影响后续分析。

3.2.2 插值法:更精细的补全策略

插值法利用已有数据推断缺失值。常用的有线性插值、多项式插值、时间序列插值等。对于风电场数据,我个人偏爱线性插值,因为它简单且物理意义明确——风速和功率在短时间内变化是连续的。

# 线性插值示例
df_interpolated = df.interpolate(method='linear', limit_direction='both')

# 时间序列插值(考虑时间间隔)
df['时间'] = pd.to_datetime(df['时间'])
df = df.set_index('时间')
df_interpolated = df.interpolate(method='time')
实战经验:插值法有个前提——缺失值不能太多,且数据要有一定的规律性。我曾经处理过一段连续缺失12小时的数据,用插值补出来的功率曲线完全失真,后来还是找运维日志手动补的。所以,插值适合「小缺口」,大缺口建议用其他方法。

3.3 数据标准化:让不同量纲的数据「对齐」

风电场数据里,风速单位是m/s,功率是kW,温度是℃……量纲不同,直接扔进模型会导致某些特征被放大。标准化的目的,就是把所有特征拉到同一个尺度上。

3.3.1 Z-score标准化:最常用的方法

Z-score把数据转换成均值为0、标准差为1的分布。公式很简单:z = (x - μ) / σ。它适合数据近似正态分布的情况。

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
df_scaled = pd.DataFrame(
    scaler.fit_transform(df[['风速', '功率', '温度']]),
    columns=['风速', '功率', '温度']
)

3.3.2 Min-Max标准化:把数据压缩到[0,1]区间

Min-Max标准化适合数据有明确边界的情况,比如风速0~25m/s。公式:x' = (x - min) / (max - min)。

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler(feature_range=(0, 1))
df_scaled = pd.DataFrame(
    scaler.fit_transform(df[['风速', '功率']]),
    columns=['风速', '功率']
)
避坑指南:标准化一定要在划分训练集和测试集之后做!我曾经犯过这个错——先对整个数据集标准化,再划分,结果测试集的信息泄露到了训练集里,模型评估虚高。正确的做法是:用训练集的参数(均值、标准差)去标准化测试集。

3.4 知识体系总览

下面这张图总结了本章的核心逻辑,我画成了流程图,方便你对照着看。

数据清洗实战流程 原始数据 异常值检测 阈值法 四分位法 缺失值处理 删除法 插值法 数据标准化 Z-score Min-Max 干净数据

3.5 实战小结

数据清洗没有银弹,关键是根据数据特点灵活选择。我个人的工作流是:

  1. 先看数据概览:用describe()和info()快速了解缺失和分布
  2. 可视化检查:画箱线图看异常,画折线图看缺失模式
  3. 异常值处理:物理阈值优先,统计方法辅助
  4. 缺失值处理:小缺口插值,大缺口删除或标记
  5. 标准化:根据模型需求选择Z-score或Min-Max
最后说一句:数据清洗占了我整个项目60%的时间,但这是值得的。干净的数据是分析的基础,也是模型性能的保障。别嫌麻烦,一步到位比后期返工强得多。

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