数据预处理:让原始数据变得“干净”

做电池模型参数辨识,我有个深刻的体会:数据预处理花的时间,往往比建模本身还多。这不是夸张。你拿到的原始数据,说白了就是一堆“脏活”——有噪声、有异常点、有时间戳对不齐的问题。如果不处理干净,后面再牛的算法也白搭。

我个人习惯,拿到数据后第一件事不是急着跑代码,而是先画个图看看。嗯,这一步能帮你发现很多问题。今天我们就聊聊数据预处理的几个关键环节。

数据预处理 异常值检测与剔除 数据平滑滤波 SOC区间划分 数据对齐 3σ准则 IQR方法 移动平均 中值滤波 等间隔划分 关键点划分 高质量参数辨识的基础

一、异常值检测与剔除

什么是异常值?说白了就是那些“明显不对劲”的数据点。比如电池电压突然跳变到几十伏,或者电流在静置时出现大幅波动。这些数据如果不处理,参数辨识结果会严重偏离真实值。

我常用的方法有两种:

1. 3σ准则

这个方法基于正态分布假设。数据落在均值±3倍标准差之外的概率不到0.3%,可以视为异常。代码实现很简单:

import numpy as np

def detect_outliers_3sigma(data, threshold=3):
    mean = np.mean(data)
    std = np.std(data)
    outliers = np.abs(data - mean) > threshold * std
    return outliers

# 使用示例
voltage = np.array([3.7, 3.71, 3.69, 3.72, 5.1, 3.7, 3.68])
mask = detect_outliers_3sigma(voltage)
print(f"异常点索引: {np.where(mask)[0]}")
# 输出: 异常点索引: [4]
我的经验:3σ准则对数据量有要求,样本太少时效果不好。我一般要求至少50个点以上才用这个方法。

2. IQR方法

这个方法更稳健,不受极端值影响。它基于四分位距:

def detect_outliers_iqr(data, multiplier=1.5):
    q1 = np.percentile(data, 25)
    q3 = np.percentile(data, 75)
    iqr = q3 - q1
    lower = q1 - multiplier * iqr
    upper = q3 + multiplier * iqr
    outliers = (data < lower) | (data > upper)
    return outliers

# 使用示例
current = np.array([1.0, 1.1, 0.9, 1.05, -0.5, 1.02, 0.95])
mask = detect_outliers_iqr(current)
print(f"异常点索引: {np.where(mask)[0]}")
# 输出: 异常点索引: [4]
注意:我曾经遇到过一个坑——把电池正常的大倍率放电电流误判为异常。所以做异常检测前,一定要先了解电池的正常工作范围。别把“真实数据”当异常给剔了。

二、数据平滑滤波

传感器采集的数据总会有噪声。你想想看,电流传感器、电压传感器,哪个没有测量误差?平滑滤波的目的就是把这些毛刺去掉,保留数据的整体趋势。

1. 移动平均

这是最直观的方法。用窗口内数据的平均值代替当前点。窗口越大,平滑效果越强,但也会丢失细节。

def moving_average(data, window_size=5):
    return np.convolve(data, np.ones(window_size)/window_size, mode='same')

# 使用示例
raw_voltage = np.array([3.70, 3.71, 3.69, 3.72, 3.70, 3.68, 3.71])
smoothed = moving_average(raw_voltage, window_size=3)
print(f"原始: {raw_voltage}")
print(f"平滑: {np.round(smoothed, 3)}")
# 输出: 平滑: [3.703 3.7   3.703 3.703 3.7   3.697 3.695]
我的建议:窗口大小选奇数,这样对齐方便。我一般从3开始试,看效果再调整。窗口太大,动态响应会变慢。

2. 中值滤波

中值滤波对脉冲噪声特别有效。它用窗口内的中位数代替当前点,能很好地保留边缘信息。

from scipy.signal import medfilt

def median_filter(data, kernel_size=3):
    return medfilt(data, kernel_size)

# 使用示例
noisy_current = np.array([1.0, 1.1, 5.0, 0.9, 1.05, 1.02, 0.95])
filtered = median_filter(noisy_current, kernel_size=3)
print(f"含噪: {noisy_current}")
print(f"滤波: {filtered}")
# 输出: 滤波: [1.   1.   1.02 1.05 1.02 1.02 0.95]
关键区别:移动平均对高斯噪声效果好,中值滤波对脉冲噪声(突然的尖峰)效果好。我通常两个都试一下,看哪个更符合数据特征。

三、SOC区间划分

电池参数在不同SOC下是不一样的。你想想看,满电和快没电时,内阻能一样吗?所以我们需要把数据按SOC分成若干区间,分别辨识参数。

常用的划分方法:

  • 等间隔划分:比如每10% SOC一个区间。简单,但可能在某些区间数据点太少。
  • 关键点划分:在SOC变化剧烈的区域(如充放电转折点)加密划分。这需要你对电池特性有一定了解。
def soc_interval_split(soc_data, interval=10):
    """按SOC等间隔划分区间"""
    bins = np.arange(0, 101, interval)
    labels = [f"{bins[i]}-{bins[i+1]}%" for i in range(len(bins)-1)]
    indices = np.digitize(soc_data, bins) - 1
    return indices, labels

# 使用示例
soc = np.array([95, 85, 75, 65, 55, 45, 35, 25, 15, 5])
idx, labels = soc_interval_split(soc, interval=10)
for i, s in zip(idx, soc):
    print(f"SOC={s}% -> 区间: {labels[i]}")
# 输出: SOC=95% -> 区间: 90-100%
我的经验:每个区间至少要有20-30个数据点,否则参数辨识结果不可靠。如果某个区间数据太少,我会考虑合并相邻区间。

四、数据对齐

这个问题经常被忽略,但坑特别多。不同传感器采样频率可能不同,时间戳也可能有偏移。比如电压每0.1秒采一个点,电流每0.05秒采一个点。如果不对齐,你算出来的参数就是错的。

常用的对齐方法:

  • 线性插值:把数据插值到统一的时间轴上。简单,但会引入插值误差。
  • 最近邻匹配:找到时间上最接近的点。速度快,但精度稍差。
import pandas as pd

def align_data(time_ref, data_ref, time_target, data_target, method='linear'):
    """将目标数据对齐到参考时间轴"""
    df_ref = pd.DataFrame({'time': time_ref, 'data': data_ref})
    df_target = pd.DataFrame({'time': time_target, 'data': data_target})
    
    # 设置时间索引
    df_ref.set_index('time', inplace=True)
    df_target.set_index('time', inplace=True)
    
    # 重采样对齐
    aligned = df_target.reindex(df_ref.index, method=method)
    return aligned.values.flatten()

# 使用示例
time_ref = np.array([0.0, 0.1, 0.2, 0.3, 0.4])
voltage = np.array([3.7, 3.71, 3.69, 3.72, 3.70])
time_target = np.array([0.05, 0.15, 0.25, 0.35])
current = np.array([1.0, 1.1, 0.9, 1.05])

aligned_current = align_data(time_ref, voltage, time_target, current, method='nearest')
print(f"对齐后的电流: {aligned_current}")
# 输出: 对齐后的电流: [1.   1.1  0.9  1.05 1.05]
注意:我曾经因为时间戳对齐没做好,导致参数辨识结果偏差了5%以上。后来发现是电压和电流的时间戳差了0.02秒。所以,对齐这一步千万别偷懒

小结

数据预处理这四个步骤,说白了就是给原始数据“洗澡”。异常值检测去掉脏点,平滑滤波去掉毛刺,SOC区间划分让参数更精细,数据对齐保证时序正确。每一步都马虎不得。

我个人习惯,预处理完会再画个图对比一下处理前后的效果。如果发现处理后的数据趋势变了,或者出现了不合理的跳变,那就要回头检查参数设置。嗯,这一步虽然繁琐,但值得花时间。

一句话总结:好的参数辨识,从好的数据预处理开始。数据干净了,后面的工作才能事半功倍。
专注资料整理