第2章 数据采集与预处理:电池测试数据来源、电压/电流/温度数据清洗、异常值处理与插值方法

各位同学,欢迎来到第二章。

上一章我们聊了SOH和寿命预测的整体框架。今天要讲的,是整条流水线的「原材料」——数据。

我常说一句话:垃圾数据进,垃圾模型出。你模型再花哨,数据是脏的,结果就是废的。我在项目里见过太多人,花80%时间调模型,却不愿意花20%时间洗数据。结果呢?模型在测试集上跑得飞起,一上实车就崩。嗯,这里要注意,数据预处理不是体力活,是技术活。

2.1 电池测试数据来源

先说说数据从哪来。说白了,就三个渠道:

  • 实验室测试数据:这是最干净的。用充放电柜跑循环,电压、电流、温度、容量,一条龙记录。我习惯用Arbin或Maccor的柜子,采样频率一般设到1Hz到10Hz。
  • 实车运行数据:BMS上传的云端数据。这玩意儿就脏了。采样频率低(0.1Hz到1Hz),还经常丢包、跳变。你想想看,车在颠簸路上跑,传感器信号能稳定吗?
  • 公开数据集:比如NASA的电池数据集、CALCE的数据集。适合做研究,但跟实际工程场景有差距。

我个人建议:能用实验室数据就别用实车数据。但现实是,你往往只有实车数据。那就得学会怎么「擦干净」它。

2.2 电压/电流/温度数据清洗

数据清洗,我把它拆成三步走。

2.2.1 缺失值处理

实车数据最常见的毛病就是缺失。比如GPS信号丢了,温度传感器掉线了。怎么办?

  • 直接删除:如果缺失比例小于5%,且是随机缺失,删掉最省事。
  • 向前填充:用上一时刻的值补。适合温度这种变化慢的信号。
  • 线性插值:用前后两个有效值拉一条直线。适合电压、电流这种连续信号。

我曾经遇到过一个项目,BMS上报的电流数据每隔10分钟就丢一个点。用向前填充补上后,算出来的容量误差直接飙到8%。后来换成线性插值,误差降到2%以内。所以,选对方法很重要

2.2.2 噪声滤波

电流和电压信号,高频噪声特别多。尤其是电流,电机控制器一开关,毛刺就出来了。

我常用的方法是滑动平均滤波。窗口大小选5到10个点。太大信号会滞后,太小滤不干净。

import pandas as pd
import numpy as np

def moving_average_filter(data, window=5):
    """滑动平均滤波"""
    return data.rolling(window=window, center=True).mean()

# 示例:对电流信号滤波
current_raw = pd.Series([10.2, 10.5, 11.8, 10.3, 10.1, 9.9, 10.0])
current_filtered = moving_average_filter(current_raw, window=3)
print(current_filtered)

你想想看,11.8那个点明显是毛刺。滤波后,它被平滑掉了。

2.2.3 时间戳对齐

电压、电流、温度,三个信号采样频率可能不一样。电压1Hz,电流10Hz,温度0.1Hz。不对齐,你没法做后续的容量积分。

我的做法是:以最低频率的信号为基准,重采样。比如温度0.1Hz,那就把电压和电流都降采样到0.1Hz。用均值降采样,别用瞬时值。

核心原则:宁可损失时间分辨率,也要保证数据一致性。

2.3 异常值处理与插值方法

异常值,就是那些明显不合理的数据点。比如电压突然跳到100V,电流变成负的1000A。这些数据不处理,模型直接学歪。

2.3.1 异常值检测

我常用的方法有三种:

  • 3σ原则:数据在均值±3倍标准差之外,视为异常。适合正态分布的数据。
  • IQR方法:四分位距法。数据在Q1-1.5*IQR到Q3+1.5*IQR之外,视为异常。适合非正态分布。
  • 物理约束:这是最靠谱的。比如锂电池电压范围2.5V-4.2V,超出这个范围直接标异常。我在项目中就靠这招,一次抓出过3000多个传感器故障点。
def detect_outliers_iqr(data):
    """IQR方法检测异常值"""
    Q1 = data.quantile(0.25)
    Q3 = data.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return (data < lower_bound) | (data > upper_bound)

# 示例
voltage = pd.Series([3.7, 3.8, 3.9, 4.0, 5.2, 3.8, 3.7])
outliers = detect_outliers_iqr(voltage)
print(outliers)  # 5.2那个点会被标为True

2.3.2 插值方法

检测出异常值后,不能直接删。删了时间序列就断了。得插值补上。

插值方法 适用场景 我的评价
线性插值 电压、电流等连续信号 简单够用,首选
三次样条插值 温度等平滑信号 效果好,但计算量大
前向填充 SOC、SOH等状态量 别用在电压电流上
多项式插值 短时间缺失(<5个点) 容易过拟合,慎用

我个人习惯:能用线性就别用高阶。为什么?因为电池信号本质上是物理过程,变化有惯性。线性插值已经能很好地近似了。我曾经试过用三次样条插值补电压,结果在拐点处出现了过冲,反而引入了新误差。

避坑指南:插值前一定要先检查异常值是否连续。如果连续10个点都是异常,别插值了,直接整段删除。我曾经遇到过一段连续1分钟的电流数据全是0,明显是传感器掉线了。硬插值只会制造假数据。

2.4 实战:一个完整的清洗流程

最后,我给大家一个完整的清洗流程。这是我做项目时的标准操作:

  1. 加载数据:读CSV,检查时间戳是否递增。
  2. 物理约束过滤:电压2.5-4.2V,电流-300A到300A,温度-20°C到60°C。超出直接标NaN。
  3. IQR检测:对每个信号单独做,标出异常点。
  4. 缺失值插值:连续缺失≤5个点用线性插值,>5个点整段删除。
  5. 滑动平均滤波:窗口大小5,滤掉高频噪声。
  6. 重采样对齐:统一到1Hz,用均值降采样。
def clean_battery_data(df):
    """电池数据完整清洗流程"""
    # 1. 物理约束
    df.loc[(df['voltage'] < 2.5) | (df['voltage'] > 4.2), 'voltage'] = np.nan
    df.loc[(df['current'] < -300) | (df['current'] > 300), 'current'] = np.nan
    
    # 2. IQR检测
    for col in ['voltage', 'current', 'temperature']:
        outliers = detect_outliers_iqr(df[col])
        df.loc[outliers, col] = np.nan
    
    # 3. 线性插值(连续缺失≤5个点)
    df = df.interpolate(method='linear', limit=5)
    
    # 4. 删除连续缺失过多的段
    df = df.dropna(thresh=3)  # 一行至少3个非空值
    
    # 5. 滑动平均滤波
    df['current'] = moving_average_filter(df['current'], window=5)
    
    return df

警告:清洗后的数据一定要做可视化检查。画个图,看看电压曲线是不是平滑的,电流有没有奇怪的跳变。我见过有人跑完清洗流程直接扔进模型,结果发现温度曲线出现了一个大坑——原来是插值把异常值当正常值补了。所以,永远相信你的眼睛,而不是代码

好了,这一章就到这里。数据预处理是脏活累活,但也是最能体现工程师功底的地方。下一章,我们会聊特征工程——怎么从清洗后的数据里,挖出对SOH预测最有用的特征。

记住:数据洗得干净,模型才能跑得稳