4. 数据预处理:缺失值处理、异常值检测、数据平滑与滤波、数据标准化/归一化
各位同学,咱们直接进入正题。电池数据预处理,说白了就是给原始数据「洗澡」。你想想看,从实验室或者BMS(电池管理系统)里拿到的数据,那叫一个脏——传感器偶尔抽风、通信丢包、噪声干扰,啥情况都有。我做了这么多年电池建模,可以负责任地告诉你:预处理做不好,后面模型再花哨也是白搭。
核心观点:数据预处理不是可选项,是必选项。它直接决定了模型的上限。
4.1 缺失值处理
电池数据里缺失值太常见了。比如电压采集通道偶尔掉线,或者某个时间戳的数据没传上来。我个人习惯,拿到数据第一件事就是检查缺失率。
import pandas as pd
import numpy as np
# 检查缺失值
df = pd.read_csv('battery_data.csv')
print(df.isnull().sum())
print(f"缺失率: {df.isnull().mean() * 100:.2f}%")
处理缺失值,我一般分三种情况:
- 缺失率 < 5%:直接删除该行。数据量大,不差这几条。
- 缺失率 5% - 20%:用插值法。电池数据通常是时间序列,线性插值或样条插值效果不错。
- 缺失率 > 20%:这个特征基本废了。我建议直接扔掉这个特征列,别犹豫。
我的经验:我曾经处理过一批高温循环数据,电压缺失率高达30%。当时我硬着头皮用插值补全,结果模型训练出来一塌糊涂。后来发现,缺失的那段时间电池内部发生了不可逆的副反应,插值完全扭曲了真实物理过程。所以,高缺失率特征,果断放弃。
# 线性插值示例
df['voltage'] = df['voltage'].interpolate(method='linear')
# 前向填充(适用于短时间缺失)
df['current'] = df['current'].fillna(method='ffill')
4.2 异常值检测
异常值比缺失值更隐蔽。电池数据里,异常值通常来自传感器尖峰噪声或者通信错误。比如电压突然跳变到几十伏,或者电流出现负值(充电时)。
我常用的方法有三种:
| 方法 | 适用场景 | 缺点 |
|---|---|---|
| 3σ原则 | 数据近似正态分布 | 对非正态分布效果差 |
| IQR(四分位距) | 任意分布,鲁棒性强 | 对极端值不敏感 |
| 局部异常因子(LOF) | 高维数据,局部异常 | 计算量大 |
我个人最常用的是IQR方法。为什么呢?因为电池数据往往不是正态分布,尤其是SOC(荷电状态)不同区间,数据分布差异很大。IQR不依赖分布假设,用起来省心。
# IQR异常值检测
Q1 = df['voltage'].quantile(0.25)
Q3 = df['voltage'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = df[(df['voltage'] < lower_bound) | (df['voltage'] > upper_bound)]
print(f"检测到 {len(outliers)} 个异常值")
注意:别把电池的正常行为当成异常。比如电池在低温下电压会骤降,这不是异常,是物理特性。我曾经有个同事,直接把低温数据全当异常删了,结果模型在低温场景下完全失效。所以,异常值检测一定要结合物理背景。
4.3 数据平滑与滤波
电池数据里噪声是躲不掉的。尤其是电流数据,高频噪声特别多。平滑滤波的目的,就是去掉噪声,保留趋势。
我常用的滤波方法:
- 移动平均:简单粗暴,适合离线处理。窗口大小我一般取5-10个点。
- 中值滤波:对尖峰噪声效果极好。我处理电压数据时特别喜欢用。
- 卡尔曼滤波:适合在线应用。如果你要做实时SOC估计,这个必须掌握。
# 移动平均
df['current_smooth'] = df['current'].rolling(window=5).mean()
# 中值滤波
from scipy.signal import medfilt
df['voltage_med'] = medfilt(df['voltage'], kernel_size=5)
避坑指南:我曾经在做一个快速充电数据集时,用了太大的移动平均窗口(20个点),结果把充电平台期的特征给平滑没了。模型训练出来,对充电阶段的预测精度惨不忍睹。所以,窗口大小一定要根据采样频率来定。一般采样频率越高,窗口可以适当大一些。
4.4 数据标准化/归一化
这一步很多人容易忽略。电池数据里,电压、电流、温度、容量,量纲完全不同。电压是伏特(3-4V),电流是安培(0-100A),温度是摄氏度(-20-60℃)。如果不做标准化,模型会天然偏向数值大的特征。
我常用的两种方法:
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-score标准化 | x' = (x - μ) / σ | 数据近似正态分布,有异常值 |
| Min-Max归一化 | x' = (x - min) / (max - min) | 数据有明确边界,如电压、SOC |
我个人习惯:对于电池数据,电压和SOC用Min-Max归一化,因为它们的物理范围是固定的(比如电压3.0-4.2V)。电流和温度用Z-score标准化,因为它们的分布更分散,而且可能有极端工况。
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# Z-score标准化
scaler_std = StandardScaler()
df[['current', 'temperature']] = scaler_std.fit_transform(df[['current', 'temperature']])
# Min-Max归一化
scaler_mm = MinMaxScaler(feature_range=(0, 1))
df[['voltage', 'soc']] = scaler_mm.fit_transform(df[['voltage', 'soc']])
重要提醒:标准化/归一化的参数(均值、标准差、最大最小值)一定要用训练集计算,然后应用到验证集和测试集。千万别在整个数据集上统一做,否则会造成数据泄露,模型评估结果会虚高。这个坑我踩过,后来被审稿人直接怼了回来。
知识体系总览
下面这张图,把数据预处理的四个步骤串起来了。你可以把它当作操作手册,每次处理数据前看一眼。
嗯,以上就是数据预处理的全部内容。每一步都有它的道理,也有它的坑。做电池寿命预测,数据质量决定了模型的天花板。预处理花的时间,后面建模会加倍还给你。