4. 数据预处理:数据清洗、归一化与时间对齐
好,咱们接着聊。上一章我们把数据源和采集方式搞定了,但说实话,从现场拿回来的原始数据,那叫一个「脏」。我做了这么多年楼宇节能项目,见过太多团队在数据预处理上栽跟头——模型建得再漂亮,数据一塌糊涂,结果全是白搭。
这一章,咱们就专门来收拾这些「脏数据」。说白了,数据预处理就是给原始数据「洗澡」,洗完了才能喂给模型吃。
4.1 缺失值处理:别让空值坑了你
先说说缺失值。你想想看,传感器偶尔掉线、网络波动、采集器重启……这些情况在楼宇现场太常见了。我见过一个项目,某层楼的温度传感器因为接线松动,每天下午3点到4点准时丢数据,整整丢了一个月才被发现。
缺失值怎么处理?我一般分三步走:
- 先诊断:看看缺失比例。低于5%的,直接删掉对应时间点;超过30%的,建议重新采集或补测。
- 再填充:5%~30%之间的,用插值法填充。
- 最后标记:填充过的数据点,我习惯加个标记列,方便后续排查。
常用填充方法:
- 前向填充:用上一个有效值填充。适合温度、湿度这类变化缓慢的参数。
- 线性插值:用前后两个有效值做线性计算。适合压力、流量等连续变化的数据。
- 均值填充:用同一天同一时刻的历史均值。适合有周期性规律的数据。
举个例子,我处理过一栋办公楼的空调能耗数据,某天中午12:00~12:15的数据丢了。因为空调负荷在午休时段变化不大,我用前向填充就搞定了。但如果是冷水机组的出水温度,变化快,我就得用线性插值。
# Python示例:缺失值处理
import pandas as pd
import numpy as np
# 读取原始数据
df = pd.read_csv('energy_data.csv', index_col='timestamp', parse_dates=True)
# 检查缺失值
print(df.isnull().sum())
# 前向填充(适合缓慢变化的数据)
df['temp_zone1'].fillna(method='ffill', inplace=True)
# 线性插值(适合快速变化的数据)
df['chiller_power'].interpolate(method='linear', inplace=True)
# 标记填充过的数据点
df['filled_flag'] = df['chiller_power'].isnull().astype(int)
我的小习惯:填充完成后,我会把填充前后的数据画在一张图上对比一下。如果填充曲线出现明显「断崖」或「尖峰」,说明填充方法选错了,得换一种。
4.2 异常值处理:揪出那些「离谱」的数据
异常值比缺失值更隐蔽,也更危险。我遇到过最离谱的一次:某栋楼的电表因为雷击,连续三天读数飙到正常值的10倍,但系统没报警,因为数据「看起来」是连续的。结果那三天的能耗分析报告直接误导了节能策略的制定。
异常值怎么判断?我常用的方法就两种:
| 方法 | 适用场景 | 阈值设定 |
|---|---|---|
| 3σ原则 | 数据近似正态分布 | 均值±3倍标准差 |
| IQR四分位距 | 数据分布偏态严重 | Q1-1.5×IQR 到 Q3+1.5×IQR |
我个人更偏爱IQR方法,因为楼宇能耗数据很少是完美的正态分布。你想想看,空调负荷在夏天和冬天完全两个样,用3σ很容易把冬天的正常值误判为异常。
注意:异常值不一定是「坏数据」。比如某天突然出现一个能耗高峰,可能是设备故障,也可能是人为操作(比如加班)。我建议先标记出来,交给运维人员确认,不要一删了之。
# Python示例: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[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers
# 检测空调能耗的异常值
outliers = detect_outliers_iqr(df, 'hvac_power')
print(f"发现 {len(outliers)} 个异常数据点")
4.3 数据归一化:让不同量纲的数据「平起平坐」
归一化这件事,说白了就是把不同尺度的数据拉到同一个量级。比如温度是20~30℃,功率是100~500kW,如果不归一化,模型会天然地「偏爱」数值大的特征。
我常用的归一化方法就两种:
- Min-Max归一化:把数据缩放到[0,1]区间。适合数据分布比较均匀的情况。
- Z-score标准化:把数据变成均值为0、标准差为1的分布。适合数据有异常值的情况。
嗯,这里要注意:归一化的参数(比如最大值、最小值、均值、标准差)一定要用训练集的数据计算,然后应用到测试集上。我曾经见过有人把整个数据集一起归一化,结果模型在线上部署时直接崩了——因为新数据的最大值超出了训练集的范围。
# Python示例:Min-Max归一化
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
# 只对数值列做归一化
numeric_cols = ['temp_zone1', 'hvac_power', 'lighting_power']
df[numeric_cols] = scaler.fit_transform(df[numeric_cols])
避坑指南:我曾经在一个项目中,把归一化后的数据直接用于可视化,结果发现温度曲线和功率曲线完全重合了,根本看不出变化趋势。后来我学乖了:归一化只用于模型训练,可视化还是用原始数据。
4.4 时间序列对齐:把不同频率的数据「对齐」
最后一步,也是很多人容易忽略的一步——时间对齐。楼宇里的数据采集频率五花八门:电表可能每15分钟一个点,温度传感器每5分钟一个点,气象站每小时一个点。如果不对齐,模型根本没法用。
我的做法是:统一降采样到最低频率。比如上面这个例子,我就统一到每小时一个点。为什么?因为降采样不会丢失信息,但升采样会「无中生有」地创造数据。
具体操作:
- 确定目标频率(比如1小时)。
- 对高频数据做聚合(取均值、最大值或最小值)。
- 对低频数据做前向填充或插值。
# Python示例:时间对齐到1小时
# 对15分钟的电表数据做聚合
df_elec_hourly = df_elec.resample('1H').mean()
# 对5分钟的温度数据做聚合
df_temp_hourly = df_temp.resample('1H').mean()
# 合并所有数据
df_aligned = pd.concat([df_elec_hourly, df_temp_hourly], axis=1)
特别提醒:时间对齐时,一定要注意时区问题。我遇到过最坑的一次:某栋楼的电表用的是UTC时间,而温度传感器用的是本地时间,两者差了8个小时。如果不做时区转换,分析结果完全错位。
好了,数据预处理这块就讲到这里。总结一下我的经验:缺失值要「先诊断后填充」,异常值要「先标记后确认」,归一化要「分训练集和测试集」,时间对齐要「统一降采样」。这些步骤看起来繁琐,但做扎实了,后面的建模工作会顺畅很多。
下一章,咱们聊聊特征工程——怎么从这些「洗干净」的数据里,挖出真正有用的信息。