4. 数据清洗与预处理:缺失值处理、异常值检测、数据归一化与标准化
各位同学,咱们接着往下聊。上一章我们把EMS系统的数据源和采集逻辑理清了,但说实话,从现场拿到的数据,那叫一个“原生态”。
我干这行十几年,见过最离谱的一次——某工厂的电力数据表里,居然有连续三天全是0。后来一查,是采集终端掉线了,但系统还在“兢兢业业”地记录。你想想看,要是拿这种数据去做能效分析,结果能看吗?
所以,数据清洗与预处理,不是“锦上添花”,而是“雪中送炭”。这一步做不好,后面所有的模型、算法、收益计算,全是空中楼阁。
核心观点:数据预处理占整个数据分析工作量的60%-80%。别嫌烦,这是基本功。
4.1 缺失值处理——别让“空”坑了你
缺失值,说白了就是数据表里那些“空着的格子”。在EMS系统里,最常见的原因就是传感器故障、通信中断、或者数据入库时出了异常。
我个人习惯,拿到数据第一件事,先跑个 df.isnull().sum(),看看哪些列有缺失,缺了多少。心里先有个底。
小技巧:如果缺失比例超过30%,我建议直接放弃这列特征。强行填充反而会引入噪声。我在一个光伏项目中就吃过这个亏——硬填了40%的缺失辐照度数据,结果模型预测偏差大到离谱。
常见的处理方法有三种:
- 直接删除:适用于缺失比例极低(<5%)且数据量充足的情况。用
df.dropna()搞定。 - 均值/中位数填充:对于连续型变量(如功率、电压),用中位数更稳健,因为均值容易被异常值带偏。
- 插值法:时间序列数据,我强烈推荐线性插值或前向填充。比如某台设备5分钟没上报数据,用前后两个点的平均值补上,比瞎猜靠谱得多。
# 示例:缺失值处理
import pandas as pd
import numpy as np
# 模拟EMS数据
data = {
'timestamp': pd.date_range('2024-01-01', periods=10, freq='15min'),
'power_kw': [100, np.nan, 102, 98, np.nan, 105, 110, np.nan, 108, 107],
'voltage_v': [220, 221, np.nan, 219, 220, 222, 221, 220, np.nan, 221]
}
df = pd.DataFrame(data)
# 1. 查看缺失情况
print(df.isnull().sum())
# 2. 线性插值填充
df['power_kw'] = df['power_kw'].interpolate(method='linear')
df['voltage_v'] = df['voltage_v'].interpolate(method='linear')
print(df)
4.2 异常值检测——揪出那些“捣乱分子”
异常值,就是那些明显偏离正常范围的数据点。比如一台额定功率100kW的电机,突然蹦出个500kW的读数——用脚趾头想都知道是假的。
为什么会这样?可能是传感器瞬间受干扰,也可能是数据传输出错。不管原因是什么,我们必须把它们揪出来。
我常用的方法有三种,咱们一个一个说:
4.2.1 3σ原则(拉依达准则)
假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差之内。超出这个范围的,视为异常。
# 3σ异常检测
def detect_outliers_3sigma(df, column):
mean = df[column].mean()
std = df[column].std()
lower = mean - 3 * std
upper = mean + 3 * std
outliers = df[(df[column] < lower) | (df[column] > upper)]
return outliers
outliers = detect_outliers_3sigma(df, 'power_kw')
print(f"检测到 {len(outliers)} 个异常值")
注意:3σ法对数据分布敏感。如果数据本身偏态严重,比如某些设备的负载率长期在20%以下,偶尔跳到80%反而是正常工况。这时候用3σ会误杀。
4.2.2 IQR四分位距法
这个方法不依赖正态分布假设,更鲁棒。它把数据分成四等份,取Q1(25%分位)和Q3(75%分位),IQR = Q3 - Q1。通常把小于 Q1 - 1.5*IQR 或大于 Q3 + 1.5*IQR 的点视为异常。
# IQR异常检测
Q1 = df['power_kw'].quantile(0.25)
Q3 = df['power_kw'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers_iqr = df[(df['power_kw'] < lower) | (df['power_kw'] > upper)]
print(f"IQR法检测到 {len(outliers_iqr)} 个异常值")
4.2.3 DBSCAN聚类法
这个方法更高级一点。它基于密度聚类,把那些“孤零零”的点标记为异常。我在一个大型商业综合体的能效分析中用过,效果出奇的好——它甚至能发现某些设备在非工作时间的异常启停。
from sklearn.cluster import DBSCAN
import numpy as np
# 准备数据(需要二维以上)
X = df[['power_kw', 'voltage_v']].values
# 标准化后聚类
clustering = DBSCAN(eps=3, min_samples=2).fit(X)
# 标签为-1的是异常点
df['cluster'] = clustering.labels_
outliers_db = df[df['cluster'] == -1]
print(f"DBSCAN检测到 {len(outliers_db)} 个异常值")
我的经验:实际项目中,我通常把3σ和IQR结合着用。先用IQR粗筛一遍,再用3σ复核。如果两种方法都标记为异常,那基本可以确定是“真异常”。我曾经用这个组合拳,帮一个水泥厂找出了3个长期误报的传感器,一年省了十几万的运维成本。
4.3 数据归一化与标准化——让不同量纲的数据“说同一种语言”
你想想看,在EMS系统里,电压是220V左右,功率可能是几千kW,温度可能是几十度。这些数据的数值范围天差地别。如果直接扔进模型,数值大的特征会主导学习过程,数值小的特征直接被“淹没”。
所以,我们需要做归一化或标准化。说白了,就是把数据“拉”到同一个尺度上。
4.3.1 归一化(Min-Max Scaling)
把数据缩放到 [0, 1] 区间。公式很简单:X_norm = (X - X_min) / (X_max - X_min)。
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
df[['power_kw', 'voltage_v']] = scaler.fit_transform(df[['power_kw', 'voltage_v']])
print(df[['power_kw', 'voltage_v']].head())
归一化的好处是保留了原始数据的分布形状。但缺点也很明显——它对异常值非常敏感。如果数据里有个极端值,会把其他正常数据压缩到很小的区间里。
4.3.2 标准化(Z-score Scaling)
标准化是把数据变成均值为0、标准差为1的分布。公式:X_std = (X - μ) / σ。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df[['power_kw', 'voltage_v']] = scaler.fit_transform(df[['power_kw', 'voltage_v']])
print(df[['power_kw', 'voltage_v']].head())
标准化不要求数据有上下界,而且对异常值的鲁棒性比归一化好一些。我个人在大多数场景下更倾向于用标准化,尤其是当数据分布近似正态时。
选型建议:
- 如果后续算法对尺度敏感(如KNN、SVM、神经网络),必须做归一化或标准化。
- 如果数据分布未知或存在较多异常值,优先考虑标准化。
- 如果数据有明确的上下界(如百分比、效率值0-100%),用归一化更直观。
4.4 实战案例:某工厂空压机系统数据清洗
我记得去年帮一个电子厂做能效诊断,他们的空压机系统数据简直是一团乱麻。咱们拿这个案例来收尾。
| 时间戳 | 功率(kW) | 排气压力(bar) | 排气温度(℃) | 运行状态 |
|---|---|---|---|---|
| 2024-03-01 08:00 | 132.5 | 7.2 | 85.3 | 1 |
| 2024-03-01 08:15 | NaN | 7.1 | 86.0 | 1 |
| 2024-03-01 08:30 | 450.0 | 7.3 | 87.2 | 1 |
| 2024-03-01 08:45 | 131.8 | 7.0 | 84.9 | 1 |
你看这个数据:
- 08:15 功率缺失——用前后均值插值补上,得到132.15kW。
- 08:30 功率450kW——这台空压机额定功率才160kW,明显异常。用IQR法检测,Q3=133.2,上限=133.2+1.5*(133.2-131.0)=136.5,450远超上限。直接剔除,用插值重算。
- 最后把所有数值列做标准化,输入到后续的能效模型中。
经过这一套流程,数据质量从“惨不忍睹”变成了“干净利落”。后续的能效分析模型,R²从0.62直接提升到了0.91。你看,数据预处理的价值就在这里。
避坑指南:我曾经在一个项目中,为了省事,直接删除了所有含缺失值的行。结果发现删掉了将近一半的数据,导致样本量不足,模型根本没法训练。所以,删除之前一定要先评估缺失比例,别一刀切。
好了,数据清洗与预处理这块,咱们就聊到这儿。记住一句话:垃圾进,垃圾出。数据质量决定了分析结果的天花板。下一章,咱们会把这些干净的数据拿来做真正的能效指标计算,到时候你就知道今天这功夫没白花。
公众号:蓝海资料掘金营,微信deep3321