第三章:数据清洗实战——重复数据去重、时间戳对齐、数据标准化与归一化

各位同学,欢迎来到数据清洗实战环节。

说实话,在电力能耗分析这个行当里,我见过太多人拿到数据就急着建模、急着出报告。结果呢?模型跑出来一塌糊涂,报告里的数字自己看了都心虚。为什么?因为数据没洗干净。

我个人习惯,花在数据清洗上的时间,至少要占到整个项目周期的40%。别嫌多,你想想看,脏数据进去,垃圾结果出来,这道理谁都懂。但真正能做到的人,不多。

今天这一章,咱们就聚焦三个最核心的清洗动作:去重、对齐、标准化。这三板斧抡好了,你的数据质量至少提升80%。

3.1 重复数据去重——别让同一个电表读数算两遍

先说说重复数据。电力能耗数据里,重复太常见了。

我在项目里遇到过好几次,某个变电站的采集终端因为网络抖动,同一时刻的数据被重复上报了三次。如果不处理,你算出来的总能耗直接虚高30%。

3.1.1 怎么判断重复?

核心就一句话:看主键。对于电力数据,主键通常是「设备ID + 时间戳」。如果这两者完全一样,那就是重复。

关键原则:去重不是简单删掉重复行,而是要判断保留哪一条。我一般保留第一条,或者取平均值。具体看业务场景。

来看一段Python代码,用pandas处理重复数据:

import pandas as pd

# 读取原始数据
df = pd.read_csv('power_consumption.csv')

# 查看重复行数量
print(f"重复行数:{df.duplicated(subset=['device_id', 'timestamp']).sum()}")

# 去重:保留第一次出现的记录
df_clean = df.drop_duplicates(subset=['device_id', 'timestamp'], keep='first')

# 如果怀疑是采集误差,也可以取均值
# df_clean = df.groupby(['device_id', 'timestamp']).mean().reset_index()

print(f"去重后数据量:{len(df_clean)}")

小技巧:去重之前,先按时间戳排个序。这样保留的第一条往往是时间上最接近真实采集时刻的。

3.1.2 避坑指南

我曾经犯过一个错:只看设备ID去重,没考虑时间戳。结果把同一个设备不同时刻的正常数据给删了。嗯,那次被领导骂得不轻。

所以记住:去重一定要联合主键。对于电力数据,时间精度至少要到分钟级,最好到秒级。

3.2 时间戳对齐——让不同设备的数据在同一时间线上说话

接下来是时间戳对齐。这问题在电力系统里特别突出。

你想想看,一个工厂里可能有几十个电表,有的每15分钟上报一次,有的每1小时上报一次,还有的采集时间有延迟。这些数据如果不对齐,你根本没法做对比分析。

3.2.1 对齐的思路

说白了,就是选一个基准时间频率,然后把所有数据都“掰”到这个频率上来。

我个人习惯用15分钟作为基准。为什么?因为电力市场交易、需求侧响应,基本都是15分钟一个结算周期。

对齐方法有两种:

  • 上采样(Upsampling):把低频数据变成高频。比如小时数据变成15分钟数据。常用插值法。
  • 下采样(Downsampling):把高频数据变成低频。比如分钟数据聚合成15分钟数据。常用求均值或求和。

看代码:

# 假设df1是15分钟数据,df2是小时数据
# 先将时间戳设为索引
df1 = df1.set_index('timestamp')
df2 = df2.set_index('timestamp')

# 对df2进行上采样,插值到15分钟
df2_resampled = df2.resample('15T').interpolate(method='linear')

# 对df1进行下采样,聚合成15分钟(其实它本来就是15分钟,这里演示)
df1_resampled = df1.resample('15T').mean()

# 最后合并两个DataFrame
df_aligned = pd.concat([df1_resampled, df2_resampled], axis=1)

注意:插值法只适用于数据变化平缓的场景。如果负荷波动剧烈(比如电弧炉、轧钢机),插值会引入很大误差。这时候我建议用前向填充(ffill)或者干脆丢弃缺失段。

3.2.2 时间戳对齐的坑

我曾经处理过一个光伏电站的数据。逆变器上报的时间戳是UTC时间,而电表用的是本地时间。两个时间没对齐,差了8个小时。结果算出来的光伏自消纳率完全不对。

所以,对齐之前,先统一时区。我一般全部转成UTC+8,或者全部转成时间戳(Unix timestamp),这样最保险。

3.3 数据标准化与归一化——让不同量纲的数据可以放在一起比

最后这个环节,很多初学者容易忽略。

你想想看,电压是kV级别,电流是A级别,功率是MW级别,温度是°C级别。这些数据如果直接扔进模型,量纲大的特征会主导模型训练,量纲小的特征直接被淹没。

3.3.1 标准化 vs 归一化

这两个概念经常被混用,其实不一样:

方法 公式 输出范围 适用场景
归一化(Min-Max) (x - min) / (max - min) [0, 1] 数据有明确边界,比如电压、电流
标准化(Z-score) (x - mean) / std 无固定范围,均值为0 数据有异常值,或者分布未知

我个人习惯:做聚类或距离计算时用标准化,做神经网络输入时用归一化。当然这不是铁律,具体问题具体分析。

代码实现:

from sklearn.preprocessing import MinMaxScaler, StandardScaler

# 归一化
scaler_minmax = MinMaxScaler()
df['power_norm'] = scaler_minmax.fit_transform(df[['power_kw']])

# 标准化
scaler_std = StandardScaler()
df['power_std'] = scaler_std.fit_transform(df[['power_kw']])

3.3.2 避坑指南

我曾经在做一个负荷预测项目时,对训练集和测试集分别做了归一化。结果测试集的min和max跟训练集不一样,导致预测结果完全偏离。后来才意识到:要用训练集的参数去归一化测试集

正确做法:

# 只在训练集上fit
scaler.fit(X_train)
# 然后用同一个scaler去transform训练集和测试集
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

3.4 实战案例:一个完整的清洗流程

好了,理论讲完了。咱们来个完整的例子。

假设你拿到一个工厂的能耗数据,包含三个设备:A、B、C。数据有重复、时间戳不齐、量纲不同。咱们一步步来:

import pandas as pd
from sklearn.preprocessing import StandardScaler

# 1. 加载数据
df = pd.read_csv('factory_power.csv', parse_dates=['timestamp'])

# 2. 去重
df = df.drop_duplicates(subset=['device_id', 'timestamp'])

# 3. 时间戳对齐(统一重采样到15分钟)
df = df.set_index('timestamp')
df = df.groupby('device_id').resample('15T').mean().reset_index()

# 4. 标准化(只对数值列操作)
num_cols = ['voltage_kv', 'current_a', 'power_kw']
scaler = StandardScaler()
df[num_cols] = scaler.fit_transform(df[num_cols])

# 5. 保存清洗后的数据
df.to_csv('factory_power_clean.csv', index=False)

print("数据清洗完成!")

你看,核心代码就这么几行。但每一步背后的思考,才是真正值钱的东西。

3.5 本章小结

数据清洗,说白了就是给数据“洗澡”。去重是搓掉泥点,对齐是把衣服理平,标准化是让所有数据穿上统一尺码的衣服。

我做了这么多年电力数据分析,最大的体会就是:数据清洗花的时间越多,后面建模和出报告就越省心。别急着往前冲,先把地基打牢。

下一章,咱们聊聊特征工程。到时候你会发现,数据洗得干不干净,直接决定了特征好不好用。

课后练习:找一份公开的电力负荷数据(比如UCI数据集),按照本章的三步流程做一遍清洗。重点关注去重后数据量减少了多少,对齐后时间序列是否连续。