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']])

重要提醒:标准化/归一化的参数(均值、标准差、最大最小值)一定要用训练集计算,然后应用到验证集和测试集。千万别在整个数据集上统一做,否则会造成数据泄露,模型评估结果会虚高。这个坑我踩过,后来被审稿人直接怼了回来。

知识体系总览

下面这张图,把数据预处理的四个步骤串起来了。你可以把它当作操作手册,每次处理数据前看一眼。

电池数据预处理流程 原始电池数据 步骤1:缺失值处理 删除行 / 插值 / 删除特征 步骤2:异常值检测 3σ原则 / IQR / LOF 步骤3:数据平滑与滤波 移动平均 / 中值滤波 / 卡尔曼滤波 步骤4:标准化/归一化 Z-score标准化 / Min-Max归一化 关键要点 • 缺失率<5%:删除 • 缺失率5-20%:插值 • 缺失率>20%:删特征 • IQR最鲁棒 • 结合物理背景 • 窗口大小要合理 • 别平滑掉特征 • 电压/SOC用Min-Max • 电流/温度用Z-score ⚠ 防止数据泄露 参数只从训练集计算

嗯,以上就是数据预处理的全部内容。每一步都有它的道理,也有它的坑。做电池寿命预测,数据质量决定了模型的天花板。预处理花的时间,后面建模会加倍还给你。


蓝海数据掘金营,专注资料整理