第1章:数据清洗基础——缺失值填充策略、异常值剔除与重复数据删除

大家好,我是老蓝。在风场干了这么多年数据,我最大的体会就是:SCADA数据从来不会干干净净地等你来用。今天咱们就聊聊数据清洗最基础、也最绕不开的三个活儿——缺失值、异常值和重复数据。

说实话,我刚入行那会儿,拿到风场数据就直接建模,结果模型跑出来一塌糊涂。后来才发现,数据里全是坑。你想想看,传感器偶尔掉线、通讯中断、风机维护停机……这些都会在数据里留下“脏东西”。不清理干净,后面的特征工程全是白搭。

1.1 缺失值填充策略

缺失值,说白了就是数据表里那些空着的格子。我见过最夸张的项目,某个测风塔的数据缺失率高达40%。那怎么办?直接扔掉?太浪费了。咱们得想办法填上。

我个人习惯把缺失值填充分成四类,每类都有它的适用场景:

填充方法 适用场景 我的经验
均值填充 数据分布较对称,无明显异常值 风速、温度这类指标可以用
中位数填充 数据有偏态分布,或存在异常值 功率数据我常用中位数,因为偶尔有极端值
前向填充 时间序列数据,短时间缺失 SCADA的连续采集数据,缺几分钟很正常
插值法 数据变化平滑,缺失段不长 线性插值最常用,但别用在突变点

1.1.1 均值与中位数填充

这两种方法最简单,但千万别无脑用。我在项目中遇到过这样的情况:某台风机在强风时段传感器故障,导致风速数据大面积缺失。如果用均值填充,会把强风时段的数据拉低,后续算发电量就会偏小。

正确的做法是:先分组,再填充。比如按季节、按工况分组,每组分别算均值或中位数。

# 按季节分组填充均值
import pandas as pd

df['season'] = df['timestamp'].dt.month.map(lambda x: (x%12+3)//3)
df['wind_speed_filled'] = df.groupby('season')['wind_speed'].transform(
    lambda x: x.fillna(x.mean())
)
小技巧:如果数据量够大,我建议优先用中位数。均值容易被极端值带偏,中位数更稳健。尤其是功率曲线数据,中位数填充的效果往往更好。

1.1.2 前向填充

前向填充,就是拿上一个时刻的值来填当前的空缺。这个方法在SCADA数据里特别实用。为什么?因为风场数据是连续采集的,通常每10分钟一条记录。如果某条记录缺失了,大概率是通讯瞬间中断,前后时刻的值变化不大。

我曾经处理过一个项目,某台风机连续3小时数据缺失。用前向填充显然不行——3小时的风况可能完全变了。这时候我会限制填充的最大步长,比如最多往前填充6个点(1小时)。

# 限制前向填充的最大步长
df['wind_speed_ffill'] = df['wind_speed'].fillna(method='ffill', limit=6)
注意:前向填充不能滥用。如果缺失段太长(比如超过1小时),建议用插值法或者干脆标记为无效。我曾经吃过亏,用前向填充填了一整天的数据,结果模型训练出来完全不对。

1.1.3 插值法

插值法,说白了就是根据已知数据点,拟合出一条曲线,然后估算缺失点的值。线性插值是最常用的,但如果你面对的是风速这种波动较大的数据,可以考虑用样条插值。

嗯,这里要注意:插值法只适合缺失段较短的情况。如果缺失段太长,插值出来的结果基本就是瞎猜。

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

# 时间序列插值(考虑时间间隔)
df['wind_speed_interp'] = df['wind_speed'].interpolate(method='time')

1.2 异常值剔除与修正

异常值,就是那些明显不合理的数据点。比如风速突然飙到100m/s,或者功率为负值。这些数据要么是传感器故障,要么是通讯错误,必须处理掉。

我常用的方法有三种:

  • 3σ原则:超出均值±3倍标准差的数据视为异常。适合正态分布的数据。
  • IQR方法:超出Q1-1.5IQR或Q3+1.5IQR的数据视为异常。适合偏态分布。
  • 业务规则:根据物理常识设定阈值。比如风速不可能超过切出风速太多。

我个人最推荐的是业务规则+IQR的组合。为什么?因为纯统计方法可能会把真实的高风速数据误判为异常。我在一个海上风场项目里就遇到过,台风天风速确实很高,但3σ方法把它当异常剔除了,结果模型低估了极端工况下的发电能力。

# IQR方法剔除异常值
Q1 = df['wind_speed'].quantile(0.25)
Q3 = df['wind_speed'].quantile(0.75)
IQR = Q3 - Q1

lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

df_clean = df[(df['wind_speed'] >= lower_bound) & (df['wind_speed'] <= upper_bound)]
核心原则:异常值不一定要直接剔除。如果数据量少,可以考虑修正——比如用前后时刻的平均值替换。我曾经处理过一个项目,数据总共才3个月,剔除异常值后只剩2个月,后来改用修正,模型效果好了不少。

1.3 重复数据删除

重复数据,就是完全一样或者几乎一样的记录。SCADA系统偶尔会重复发送数据,或者数据入库时出现重复。重复数据会导致统计结果偏倚,比如算平均风速时,重复的那条会被多算一次。

我一般分两步走:

  1. 完全重复:所有列都一样的记录,直接删除。
  2. 近似重复:时间戳相近、数值也相近的记录,需要人工判断。
# 删除完全重复的记录
df = df.drop_duplicates()

# 按指定列判断重复(比如时间戳)
df = df.drop_duplicates(subset=['timestamp'], keep='first')
避坑指南:我曾经遇到过一种情况——两条记录时间戳完全相同,但数值不同。这通常是传感器同时上报了两条数据。这时候不能简单删除,需要确认哪条是有效的。我的做法是保留平均值,或者标记为异常由人工复核。

1.4 本章知识体系

下面这张图是我自己总结的数据清洗流程,你可以把它当作操作手册来用:

数据清洗基础流程 原始SCADA数据 步骤1:缺失值填充 步骤2:异常值剔除与修正 步骤3:重复数据删除 干净数据

这张图看起来简单,但每一步都有很多细节。我建议你在实际项目中,按照这个顺序走一遍。别跳步,也别偷懒。数据清洗虽然枯燥,但它是整个数据分析流程的基石。

我的经验:数据清洗通常占整个项目时间的60%以上。别嫌麻烦,这一步做扎实了,后面的建模、分析都会顺风顺水。我曾经因为赶进度,数据清洗只做了30%,结果模型上线后频繁报错,最后返工花了3倍时间。

好了,这一章的内容就到这里。数据清洗是个手艺活,多练才能找到感觉。下一章咱们聊聊特征工程里的时间特征提取,到时候见。


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