第4章:数据采集与预处理
智能电表数据,说白了就是需求响应算法的“口粮”。
我见过不少团队,模型结构设计得漂漂亮亮,结果数据一喂进去,训练直接崩了。为什么?数据太脏。今天咱们就聊聊,怎么把这堆原始读数,变成模型能吃的干净数据。
4.1 智能电表数据采集
先说说数据从哪来。智能电表现在基本是标配了,15分钟一个采样点,一天96个点。我习惯把采集频率分成三类:
- 高频采集:1分钟或更短,适合做负荷预测的精细模型
- 中频采集:15分钟,这是最常见的,也是我项目里用得最多的
- 低频采集:1小时,适合做长期趋势分析
采集的时候,有个坑你得注意——时间戳对齐。不同电表可能有时差,甚至有的电表会跳变。我遇到过一台电表,每天凌晨3点准时跳快5分钟,排查了好久才发现是固件bug。
我的小习惯:采集数据时,我会额外记录一个“采集状态码”。0表示正常,1表示通信异常,2表示电表故障。这样后期清洗时,可以直接按状态码过滤,省事很多。
4.2 数据清洗:缺失值处理
缺失值,是数据预处理里最头疼的问题。你想想看,一个96点的日负荷曲线,中间缺了3个点,模型就不知道那段时间发生了什么。
我一般按三步走:
- 检测缺失:直接看时间戳是否连续。不连续的地方,就是缺失。
- 判断缺失类型:是随机缺失,还是连续缺失一大段?
- 选择填充方法:
| 缺失情况 | 推荐方法 | 我的经验 |
|---|---|---|
| 单个点缺失 | 线性插值 | 简单有效,我90%的情况都用它 |
| 连续2-5个点缺失 | 三次样条插值 | 比线性插值更平滑,适合负荷曲线 |
| 连续超过5个点缺失 | 用前一天同一时刻的值填充 | 注意:如果前一天也缺失,就取前一周的平均值 |
| 整段缺失(比如停电) | 直接删除该时段 | 别硬填,填了反而误导模型 |
# 这是我常用的线性插值代码
import pandas as pd
# 假设df是DataFrame,'power'列是负荷数据
df['power_interpolated'] = df['power'].interpolate(method='linear')
# 如果连续缺失超过5个点,改用前一天数据
df['power_filled'] = df['power_interpolated'].fillna(
df['power'].shift(96) # 96个点对应前一天同一时刻
)
注意:千万别对所有缺失值都用同一种方法。我曾经犯过这个错,结果模型训练出来,预测值在缺失段附近全是平的,完全没反应。后来才发现,是插值把真实波动给抹平了。
4.3 数据清洗:异常值处理
异常值,比缺失值更隐蔽。电表读数突然飙到10000千瓦,你信吗?反正我不信。
我总结了几种常见的异常:
- 物理限值异常:比如居民电表读数超过50kW,基本是错的
- 突变异常:相邻两个点变化超过30%,可能是电表跳变
- 持续不变异常:连续十几个点读数一模一样,电表可能卡住了
处理异常值,我推荐用3σ原则或者箱线图法。3σ适合数据分布比较正态的情况,箱线图更鲁棒。
# 3σ异常检测
mean = df['power'].mean()
std = df['power'].std()
df['is_outlier_3sigma'] = (df['power'] - mean).abs() > 3 * std
# 箱线图法(更推荐)
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_outlier_iqr'] = (df['power'] < lower_bound) | (df['power'] > upper_bound)
避坑指南:我曾经遇到过一个案例,某工厂的电表在每天中午12点准时出现一个尖峰,用3σ检测出来全是异常。后来去现场一看,人家中午是电炉集中加热时间,那个尖峰是真实的。所以,异常值不一定是错误值,一定要结合业务逻辑判断。
4.4 数据归一化与标准化
数据洗干净了,接下来就是让模型“看懂”数据。不同电表的量纲不一样,有的最大负荷100kW,有的1000kW。如果不做归一化,模型会天然偏向数值大的特征。
我常用的两种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Min-Max归一化 | (x - min) / (max - min) | 数据分布有明确上下界,比如负荷率0-1 |
| Z-score标准化 | (x - mean) / std | 数据分布近似正态,或者有异常值 |
from sklearn.preprocessing import MinMaxScaler, StandardScaler
# Min-Max归一化
scaler_minmax = MinMaxScaler()
df['power_norm'] = scaler_minmax.fit_transform(df[['power']])
# Z-score标准化
scaler_std = StandardScaler()
df['power_std'] = scaler_std.fit_transform(df[['power']])
我个人习惯:如果后续要用神经网络,优先用Min-Max归一化,把数据压到[0,1]区间,模型收敛更快。如果是树模型,其实归一化影响不大,但为了统一流程,我还是会做。
一个小细节:归一化的时候,一定要用训练集的min和max去变换验证集和测试集。千万别把整个数据集一起归一化,那会造成数据泄露,模型评估结果会虚高。我见过有人犯这个错,上线后模型表现直接打对折。
4.5 本章知识体系
下面这张图,是我梳理的数据预处理全流程。你可以把它当成一个检查清单,每次做数据预处理时,对着走一遍。
嗯,数据预处理这块,说白了就是“脏活累活”,但也是决定模型上限的关键一步。我见过太多项目,算法调参调了几个月,效果还不如把数据好好洗一遍来得明显。所以,别嫌麻烦,每一步都走扎实了,后面模型训练才能顺风顺水。