第1章:数据预处理基础
各位同行好,我是老张。在储能电站干了快十年,每天跟海量数据打交道。说实话,数据预处理这步,看着基础,但最容易翻车。今天咱们就聊聊这块儿。
核心观点:数据预处理不是可有可无的步骤,而是决定后续分析成败的关键。我见过太多项目,算法模型再先进,数据没洗干净,结果全是废的。
一、缺失值处理
储能电站的数据采集,说白了就是各种传感器在干活。但传感器也会闹脾气——通信中断、设备故障、人为误操作,都会导致数据缺失。我遇到过最夸张的一次,一个站的SOC数据连续缺失了8个小时,后来发现是采集模块的电源线松了。
1. 删除法
最简单粗暴的方法。把有缺失的行或列直接扔掉。
import pandas as pd
import numpy as np
# 读取储能电站数据
df = pd.read_csv('bess_data.csv')
# 删除包含任何缺失值的行
df_dropna = df.dropna()
# 删除缺失比例超过50%的列
df_drop_thresh = df.dropna(thresh=len(df)*0.5, axis=1)
print(f"原始数据量: {len(df)}")
print(f"删除后数据量: {len(df_dropna)}")
注意:删除法有风险。我曾经在一个项目中直接删除了5%的缺失行,结果发现这些行恰好对应着高温工况下的数据,导致后续的温升模型严重偏差。所以,删除前一定要确认缺失机制是随机的。
2. 均值填充
用该列的平均值来填补空缺。简单,但不够精细。
# 均值填充
df['battery_voltage'].fillna(df['battery_voltage'].mean(), inplace=True)
# 按工况分组后填充(更合理)
df['temperature'] = df.groupby('operating_mode')['temperature'].transform(
lambda x: x.fillna(x.mean())
)
我的经验:均值填充适合数据波动不大的场景,比如环境温度。但对于SOC、功率这类变化剧烈的参数,均值填充会抹掉真实波动。我一般会先看看缺失值前后的数据趋势,再决定用均值还是插值。
3. 插值法
利用已知数据点,通过数学函数估算缺失值。常用的有线性插值、多项式插值、样条插值。
# 线性插值(默认)
df['power'].interpolate(method='linear', inplace=True)
# 时间序列插值(考虑时间间隔)
df['soc'].interpolate(method='time', inplace=True)
# 多项式插值(阶数不宜过高)
df['current'].interpolate(method='polynomial', order=2, inplace=True)
我个人习惯用线性插值处理短时间缺失(比如连续缺失不超过3个点)。如果缺失段较长,我会结合前后工况信息做分段插值。你想想看,电池的充放电曲线是有物理规律的,纯数学插值有时候会插出离谱的值。
二、异常值检测
异常值就是那些明显偏离正常范围的数据点。在储能电站里,可能是传感器故障、通信误码,也可能是真实的极端工况。怎么区分?这就是检测方法要解决的问题。
1. 3σ原则
假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,视为异常。
def detect_outliers_3sigma(data, column):
mean = data[column].mean()
std = data[column].std()
lower_bound = mean - 3 * std
upper_bound = mean + 3 * std
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers
# 检测电池电压异常
voltage_outliers = detect_outliers_3sigma(df, 'battery_voltage')
print(f"检测到 {len(voltage_outliers)} 个异常电压值")
避坑指南:我曾经用3σ原则处理一组功率数据,结果发现异常值全是真实的峰值功率。为什么?因为功率数据本身就不服从正态分布,它偏态严重。所以,用3σ前一定要先检查数据分布。如果偏态明显,建议用IQR方法。
2. IQR方法
基于四分位距,不依赖正态分布假设。IQR = Q3 - Q1,通常将小于Q1-1.5×IQR或大于Q3+1.5×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
# 检测SOC异常
soc_outliers = detect_outliers_iqr(df, 'soc')
print(f"IQR方法检测到 {len(soc_outliers)} 个异常SOC值")
我的建议:对于储能电站数据,我通常两种方法都用。3σ用于检测极端异常(比如电压突变为0),IQR用于检测离散异常(比如SOC跳变)。两者结合,基本能覆盖90%的异常场景。
三、数据标准化与归一化
不同参数的量纲和量级差异很大。比如电压是几百伏,电流是几十安,温度是几十度。如果不做处理,直接扔进模型,量级大的参数会主导结果。说白了,就是「以大欺小」。
1. Min-Max归一化
将数据映射到[0,1]区间。公式:X' = (X - X_min) / (X_max - X_min)
from sklearn.preprocessing import MinMaxScaler
# 初始化归一化器
scaler = MinMaxScaler(feature_range=(0, 1))
# 对多个特征进行归一化
features = ['voltage', 'current', 'temperature', 'soc']
df[features] = scaler.fit_transform(df[features])
print("归一化后的数据范围:")
print(df[features].describe())
注意:Min-Max对异常值敏感。如果数据中有极端值,归一化后正常数据会被压缩到很小的区间。我遇到过这种情况,一个传感器偶尔冒出一个超大值,结果所有正常数据都被挤到0.01-0.05之间,模型直接废了。所以,先处理异常值,再做归一化。
2. Z-score标准化
将数据转换为均值为0、标准差为1的分布。公式:X' = (X - μ) / σ
from sklearn.preprocessing import StandardScaler
# 初始化标准化器
scaler = StandardScaler()
# 标准化处理
df[features] = scaler.fit_transform(df[features])
print("标准化后的统计量:")
print(f"均值: {df[features].mean().round(2)}")
print(f"标准差: {df[features].std().round(2)}")
Z-score不依赖数据的最大值最小值,受异常值影响较小。但前提是数据近似正态分布。如果数据分布严重偏态,标准化后的效果也不理想。
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Min-Max归一化 | 数据范围已知且稳定,如电压、温度 | 保留原始分布形状,计算简单 | 对异常值敏感,新数据可能越界 |
| Z-score标准化 | 数据近似正态分布,如SOC变化量 | 受异常值影响小,适合多数模型 | 不适用于偏态分布 |
总结一下:数据预处理没有银弹。缺失值处理、异常值检测、标准化归一化,每一步都要根据数据特点来选择方法。我的习惯是:先画图看看数据长什么样,再动手处理。别一上来就套公式,数据会「骗人」的。
公众号:蓝海资料掘金营,微信deep3321