4、数据预处理:缺失值处理、异常值检测与平滑、数据归一化与标准化

各位同学,欢迎来到数据预处理这一章。

说实话,很多做电池算法的人,一上来就急着调模型。我见过太多人,数据都没洗干净,就敢往神经网络里灌。结果呢?模型训练出来,精度惨不忍睹,还找不到原因。我个人习惯,花在数据预处理上的时间,至少占整个项目周期的40%。你想想看,数据质量决定了模型性能的上限,算法只是逼近这个上限而已。

4.1 缺失值处理:别让空值毁了你的模型

电池数据采集过程中,传感器故障、通信中断、存储异常,都会导致数据缺失。我在项目中遇到过,某次测试台连续跑了三个月,结果发现中间有两天数据全是NaN。当时差点崩溃,但冷静下来一想,这恰恰是考验基本功的时候。

缺失值处理,通常有三种策略:

  • 删除法:直接扔掉缺失值所在的行或列。适用于缺失比例极低(<5%)的情况。
  • 填充法:用均值、中位数、众数、前向填充、后向填充等方式补全。
  • 插值法:利用时间序列的连续性,用线性插值、样条插值等更精细的方法。

对于电池数据,我个人强烈推荐前向填充 + 线性插值的组合。为什么?因为电池的电压、电流、温度等参数,在短时间内是连续变化的,前向填充能保留最近的有效值,线性插值则能平滑过渡。

核心原则:不要盲目删除。电池数据每一行都对应一个时间点,删除会破坏时间序列的连续性。

来看一段代码示例:

import pandas as pd
import numpy as np

# 模拟电池数据
df = pd.DataFrame({
    'time': pd.date_range('2024-01-01', periods=100, freq='1min'),
    'voltage': np.random.normal(3.7, 0.1, 100),
    'current': np.random.normal(1.0, 0.2, 100),
    'temperature': np.random.normal(25, 2, 100)
})

# 人为制造缺失值
df.loc[10:15, 'voltage'] = np.nan
df.loc[30:35, 'current'] = np.nan

# 前向填充 + 线性插值
df['voltage'] = df['voltage'].ffill().interpolate(method='linear')
df['current'] = df['current'].ffill().interpolate(method='linear')

print("缺失值处理完成,剩余缺失数:", df.isnull().sum().sum())

小技巧:如果缺失值集中在某个时间段,比如充电切换放电的瞬间,建议用前后各5个点的均值来填充,效果比单纯插值好很多。

4.2 异常值检测与平滑:揪出那些离谱的数据点

电池数据里,异常值太常见了。传感器瞬间跳变、电磁干扰、通信丢包,都会产生一个离谱的数值。比如电压突然从3.7V跳到5V,这明显不合理。

常用的异常值检测方法有:

  • 3σ原则:数据服从正态分布时,超过均值±3倍标准差的值视为异常。
  • IQR(四分位距)法:低于Q1-1.5*IQR或高于Q3+1.5*IQR的值视为异常。
  • 移动窗口法:计算滑动窗口内的均值和标准差,当前值偏离窗口均值超过3σ则标记。

我曾经踩过一个坑:用3σ原则处理电池电压数据,结果把正常的高压充电段全给删了。为什么?因为充电时电压本身就偏高,3σ的阈值设得太死。后来我改用移动窗口法,窗口大小设为30个点,效果立竿见影。

避坑指南:我曾经在某个项目中,直接用全局3σ检测异常值,结果把电池老化后期的正常数据全删了。记住,电池数据是非平稳的,一定要用局部窗口法。

检测到异常值后,怎么处理?我建议用中值滤波Savitzky-Golay滤波来平滑。中值滤波对脉冲噪声效果极好,而SG滤波能保留数据的趋势特征。

from scipy.signal import savgol_filter

# 模拟带噪声的电压数据
voltage = np.random.normal(3.7, 0.05, 200)
voltage[50] = 5.0  # 人为制造异常值
voltage[150] = 2.0

# Savitzky-Golay平滑,窗口长度11,多项式阶数3
voltage_smooth = savgol_filter(voltage, window_length=11, polyorder=3)

print("原始数据标准差:", np.std(voltage))
print("平滑后标准差:", np.std(voltage_smooth))

个人经验:SG滤波的窗口长度和多项式阶数需要调参。窗口越大越平滑,但会丢失细节;阶数越高越能拟合复杂曲线,但容易过拟合。我一般从窗口11、阶数3开始试。

4.3 数据归一化与标准化:让所有特征站在同一起跑线

电池数据里,电压是3.7V左右,电流是1A左右,温度是25℃左右,容量是3000mAh。这些数值的量级完全不同。如果不做归一化,模型会天然偏向数值大的特征,比如容量。

两种主流方法:

  • Min-Max归一化:将数据缩放到[0,1]区间。公式:X' = (X - X_min) / (X_max - X_min)
  • Z-score标准化:将数据转换为均值为0、标准差为1的分布。公式:X' = (X - μ) / σ

我个人习惯,对于电池数据,Z-score标准化更常用。为什么?因为电池数据经常有新的数据点超出历史范围,比如电池老化后内阻变大。Min-Max归一化遇到新数据会失效,而Z-score标准化能自适应。

重要提醒:归一化和标准化的参数(最小值、最大值、均值、标准差)必须从训练集计算,然后应用到验证集和测试集。千万不要用全局数据计算,否则会造成数据泄露。

来看代码:

from sklearn.preprocessing import StandardScaler, MinMaxScaler

# 模拟电池特征数据
data = np.array([
    [3.7, 1.0, 25, 3000],
    [3.8, 0.5, 28, 2950],
    [3.6, 1.5, 22, 3050],
    [3.9, 0.8, 30, 2900]
])

# Z-score标准化
scaler_std = StandardScaler()
data_std = scaler_std.fit_transform(data)

# Min-Max归一化
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(data)

print("标准化后的数据:\n", data_std)
print("归一化后的数据:\n", data_mm)

这里有个细节要注意:标准化后的数据,单位就没了。你没法直接说“这个电压是3.7V”,只能说“这个电压比均值高0.5个标准差”。所以在做结果分析时,记得把数据反标准化回去。

实战建议:如果你用深度学习模型,比如LSTM或Transformer,建议用Z-score标准化。如果用的是树模型(XGBoost、LightGBM),其实不做归一化也行,但做了也没坏处。

4.4 本章知识体系总览

为了让你更直观地理解数据预处理的完整流程,我画了一张流程图。嗯,这张图花了我不少心思,把三个核心步骤串起来了。

电池数据预处理流程 原始电池数据 缺失值处理 前向填充 | 线性插值 | 删除法 异常值检测与平滑 移动窗口法 | 中值滤波 | Savitzky-Golay滤波 归一化与标准化 Min-Max归一化 | Z-score标准化 干净可用的训练数据

你看,整个流程是串行的。缺失值不处理,异常值检测就会误判;异常值不处理,归一化就会把正常数据压缩到一个小范围。每一步都环环相扣。

好了,数据预处理的核心内容就这些。记住一句话:数据预处理做得好,模型训练没烦恼。下一章我们会讲特征工程,到时候你会感谢今天认真洗数据的自己。


公众号:蓝海资料掘金营,微信deep3321