第3章 数据预处理实战:时间序列数据清洗、异常值检测、缺失值填充方法

各位工程师朋友,欢迎来到数据预处理这一章。

说实话,在电力系统干了这么多年,我最大的体会就是:算法模型再牛,也架不住数据烂。你想想看,多能协同控制里,光伏、风电、负荷、储能……各路数据从不同传感器、不同采集系统汇过来,那叫一个“群魔乱舞”。

我个人习惯,拿到数据的第一件事,不是急着建模,而是先花60%的时间做数据预处理。别嫌慢,这一步省了,后面全是坑。

核心观点:数据预处理不是“清洁工”,而是“质检员”。它决定了后续所有分析的可靠性。

3.1 时间序列数据清洗——先把“毛刺”捋顺

时间序列数据,说白了就是带时间戳的数值流。比如光伏电站每5分钟上报的功率值,或者变电站每15分钟记录的电压数据。

清洗的第一步,是检查时间轴是否连续。我在项目中遇到过,某个风电场的数据因为GPS授时模块故障,时间戳跳了整整两个小时。如果不处理,后续的时序分析全乱套。

3.1.1 时间轴对齐与重采样

数据采集频率不一致,是家常便饭。有的传感器1分钟上报一次,有的10分钟才报一次。这时候就需要重采样。

import pandas as pd

# 假设原始数据索引是时间戳
df.index = pd.to_datetime(df.index)

# 重采样到15分钟间隔,取均值
df_resampled = df.resample('15T').mean()

# 如果某个时间段没有数据,用前向填充
df_filled = df_resampled.ffill()

嗯,这里要注意:resample 的 '15T' 表示15分钟。别写成 '15min',虽然也能用,但老版本可能不兼容。

3.1.2 重复值处理

重复数据很隐蔽。有时候是通信重发,有时候是数据库写入异常。

我曾经排查过一个微电网的功率波动问题,折腾了两天,最后发现是数据采集器重复发送了同一时刻的数据,导致平均功率计算错误。

# 检查重复行
duplicates = df[df.index.duplicated(keep='first')]

# 保留第一条,删除后续重复
df_clean = df[~df.index.duplicated(keep='first')]

小技巧:keep='first' 保留第一条记录。如果怀疑第一条也有问题,可以用 keep=False 全部删除,再插值填充。

3.2 异常值检测——揪出那些“离谱”的数据

异常值,就是那些明显偏离正常范围的数据点。比如光伏功率突然飙到5000kW,而实际装机容量才3000kW——这明显是传感器短路了。

检测方法有很多,我常用的就三种:

方法 适用场景 优点 缺点
3σ原则 数据近似正态分布 简单快速 对非正态分布效果差
箱线图法(IQR) 任意分布 鲁棒性强 对尾部数据不敏感
移动平均偏差 时间序列趋势明显 考虑局部变化 参数选择依赖经验

3.2.1 3σ原则——最直观的方法

说白了,就是假设数据服从正态分布,超过均值±3倍标准差的数据,视为异常。

import numpy as np

mean = df['power'].mean()
std = df['power'].std()
threshold = 3

# 标记异常
df['is_anomaly'] = (np.abs(df['power'] - mean) > threshold * std)

但要注意,电力负荷数据往往有昼夜规律,直接对整个序列用3σ,可能会把夜间低谷期的正常低值误判为异常。我建议按时间段分组处理。

3.2.2 箱线图法——更鲁棒的选择

箱线图基于四分位数,不受极端值影响。IQR(四分位距) = Q3 - Q1。通常认为小于 Q1 - 1.5×IQR 或大于 Q3 + 1.5×IQR 的是异常。

Q1 = df['power'].quantile(0.25)
Q3 = df['power'].quantile(0.75)
IQR = Q3 - Q1

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

df['is_anomaly'] = (df['power'] < lower_bound) | (df['power'] > upper_bound)

避坑指南:我曾经在光伏数据上直接用箱线法,结果把阴天正常低功率全标成了异常。后来加了“功率变化率”条件——如果功率突变超过装机容量的20%,才判定为异常。效果好了很多。

3.3 缺失值填充——别让数据“断片”

缺失值在电力系统中太常见了。通信中断、传感器离线、存储故障……原因五花八门。

填充方法的选择,取决于缺失的模式长度

3.3.1 前向/后向填充——短缺失的首选

如果缺失不超过3个连续点,用前向填充(用上一个值补)基本够用。比如储能SOC数据,短时间内变化不大。

# 前向填充,最多填充2个连续缺失
df['soc'].fillna(method='ffill', limit=2, inplace=True)

3.3.2 线性插值——中等长度缺失

缺失5-10个点,用线性插值。它假设数据在缺失区间内线性变化,对缓慢变化的负荷数据很合适。

df['load'].interpolate(method='linear', inplace=True)

3.3.3 时间加权插值——考虑时间间隔

如果采样间隔不均匀,用 method='time',它会根据时间戳的实际间隔加权。

df['temperature'].interpolate(method='time', inplace=True)

3.3.4 模型预测填充——长缺失的终极方案

缺失超过20个连续点,简单插值就不靠谱了。我会用ARIMA或LSTM预测缺失段。

from statsmodels.tsa.arima.model import ARIMA

# 用缺失前的数据训练模型
train_data = df['wind_speed'].dropna().iloc[-100:]
model = ARIMA(train_data, order=(2,1,2))
model_fit = model.fit()

# 预测缺失的10个点
forecast = model_fit.forecast(steps=10)

个人经验:模型填充虽然精度高,但计算量大。我一般只在关键节点(比如并网点功率)用这个方法。普通传感器数据,用插值就够了。

3.4 知识体系总览

下面这张图,是我梳理的数据预处理核心逻辑。你可以把它当作操作手册。

数据预处理核心流程 原始时间序列数据 数据清洗 时间轴对齐 | 重采样 | 重复值删除 异常值检测 3σ原则 | 箱线图法 | 移动平均偏差 缺失值填充 前向填充 | 线性插值 | 模型预测 干净可用的数据

这张图展示了从原始数据到干净数据的完整链路。每一步都有对应的技术手段,你可以根据实际情况灵活组合。

3.5 实战建议与避坑总结

最后,分享几条我用血泪换来的经验:

  • 先看数据,再动手。df.describe()df.info() 快速了解数据概况。别上来就写代码。
  • 异常检测要结合业务。 光伏功率不可能为负,但负荷功率可以为负(比如用户反送电)。阈值设置要问现场工程师。
  • 保留原始数据副本。 我习惯在预处理前做 df_raw = df.copy()。万一处理错了,还能回滚。
  • 记录每一步操作。 用日志或注释写明:什么时间、用什么方法、处理了哪些数据。方便复盘。

一句话总结:数据预处理不是技术活,是良心活。你花多少心思,数据就给你多少回报。


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