第3章:Python数据处理基础

做电池建模这几年,我越来越觉得:数据处理能力,直接决定了模型的上限。你想想看,再漂亮的等效电路模型,喂进去的数据是脏的、乱的、时间戳对不齐的,那结果能看吗?

这一章,咱们就聊聊Python里最常用的两个库——NumPy和Pandas。说白了,就是教会你怎么把电池测试数据收拾得服服帖帖。

3.1 NumPy数组操作:电池数据的"集装箱"

NumPy的数组,我习惯叫它"集装箱"。为啥?因为它能装大量数据,而且装卸效率极高。

先看个最简单的例子。假设你有一组电池的电压数据:

import numpy as np

# 创建一维数组
voltage = np.array([3.7, 3.8, 3.9, 4.0, 4.1])
print(voltage)
# 输出:[3.7 3.8 3.9 4.0 4.1]

# 创建二维数组(比如:行=时间点,列=不同电芯)
battery_data = np.array([
    [3.7, 3.8, 3.9],
    [3.6, 3.7, 3.8],
    [3.5, 3.6, 3.7]
])
print(battery_data.shape)
# 输出:(3, 3)  —— 3行3列

嗯,这里要注意:数组的维度一定要对齐。我在项目中遇到过,有人把不同长度的数据硬塞进一个数组,结果报错不说,还找半天bug。

3.1.1 数组切片与索引

电池数据通常按时间排列。你想取某段时间的数据?切片就对了:

# 取第2行到第4行(索引从0开始)
subset = battery_data[1:3, :]
print(subset)

# 取所有行的第1列(比如:所有时间点的电压)
voltage_col = battery_data[:, 1]
print(voltage_col)
我的小习惯:切片时,我总喜欢先打印.shape确认一下。别嫌麻烦,这能省下大量调试时间。

3.1.2 数组运算:向量化操作

电池数据处理中,最常用的就是批量运算。比如,把电压从伏特转为毫伏:

voltage_mv = voltage * 1000
print(voltage_mv)
# 输出:[3700. 3800. 3900. 4000. 4100.]

你看,不用写循环。这就是NumPy的向量化操作,快得飞起。

再比如,计算SOC(荷电状态)的近似值:

# 假设电压范围3.0V~4.2V对应SOC 0%~100%
soc = (voltage - 3.0) / (4.2 - 3.0) * 100
print(soc)
# 输出:[58.33 66.67 75.   83.33 91.67]
避坑指南:我曾经直接用这个公式算SOC,结果发现低温下电压偏低,SOC虚高。后来加了温度补偿才准。所以,别迷信简单线性映射

3.2 Pandas DataFrame读写:数据表格的瑞士军刀

NumPy处理数值很爽,但电池数据往往带着时间戳、电芯编号、测试条件这些"标签"。这时候,Pandas的DataFrame就派上用场了。

我个人觉得,DataFrame就像Excel表格的升级版——能自动对齐、能处理缺失值、还能做各种筛选。

3.2.1 读取CSV文件

电池测试数据最常见的格式就是CSV。读取方法很简单:

import pandas as pd

# 读取电池测试数据
df = pd.read_csv('battery_test_data.csv')
print(df.head())  # 查看前5行

但这里有个坑——时间戳的解析。很多测试设备输出的时间格式五花八门:

# 指定时间列,并解析为datetime类型
df = pd.read_csv('battery_test_data.csv', 
                 parse_dates=['timestamp'],
                 index_col='timestamp')
print(df.index)
# 输出:DatetimeIndex(['2024-01-01 10:00:00', ...])
关键点:把时间列设为索引,后续的时间序列操作会方便很多。这是我踩过坑之后才养成的习惯。

3.2.2 数据预览与基本信息

拿到数据后,我通常会先做这几步:

# 查看数据基本信息
print(df.info())

# 查看统计摘要
print(df.describe())

# 查看列名
print(df.columns)

你想想看,如果数据有几十万行,不先看看基本信息,直接上手分析,很容易出问题。

3.3 数据清洗与插值:把"脏数据"变干净

电池测试数据,几乎没有一次是干净的。传感器漂移、通信中断、人为操作失误……各种情况我都遇到过。

3.3.1 处理缺失值

最常见的缺失值表现是NaN。处理方法有三种:

# 方法1:删除含有缺失值的行
df_clean = df.dropna()

# 方法2:用前一个有效值填充(适合电压、电流这种连续变化的数据)
df_filled = df.fillna(method='ffill')

# 方法3:用线性插值(我个人最推荐)
df_interpolated = df.interpolate(method='linear')
我的经验:对于电池电压数据,我一般用线性插值。因为电压变化相对平滑,插值结果比较可靠。但如果是电流突变点,我会用前向填充。

3.3.2 异常值检测

电池数据里,偶尔会出现离谱的值。比如电压突然跳到10V——这明显是传感器故障。

# 基于3σ原则检测异常值
mean = df['voltage'].mean()
std = df['voltage'].std()
df_valid = df[(df['voltage'] > mean - 3*std) & 
              (df['voltage'] < mean + 3*std)]

嗯,这里要注意:3σ原则假设数据服从正态分布。电池充放电数据往往不服从正态分布,所以我会结合物理常识来判断。比如,锂电池电压不可能超过4.3V,低于2.5V。

3.4 时间序列处理:对齐时间轴

电池测试中,不同设备采集的数据,时间戳往往不同步。比如,电压采集器每1秒记录一次,电流采集器每0.5秒记录一次。这时候就需要重采样。

3.4.1 重采样

# 将数据重采样到1秒间隔,取平均值
df_resampled = df.resample('1S').mean()

# 如果数据有缺失,用插值填充
df_resampled = df.resample('1S').interpolate()

3.4.2 时间偏移与差分

计算电池的容量变化,常用到差分:

# 计算电流的积分(近似容量变化)
df['capacity_change'] = df['current'].cumsum() * dt  # dt是时间间隔

# 计算电压变化率
df['dv_dt'] = df['voltage'].diff() / dt
避坑指南:我曾经直接用.cumsum()算容量,结果发现数据里有一段电流是负的(充电),导致容量越算越小。后来加了绝对值才搞定。所以,一定要先理解数据的物理含义

3.5 本章知识体系

下面这张图,是我梳理的数据处理流程。你可以把它当作操作手册:

电池数据处理流程 1. 数据读取 CSV / Excel / 数据库 2. 数据预览 info() / describe() / head() 3. 数据清洗 缺失值 / 异常值处理 4. 时间序列处理 重采样 / 插值 / 差分 / 时间对齐 5. 特征工程 SOC估算 / 容量计算 / 内阻提取 6. 模型输入准备 NumPy数组 / DataFrame导出 每个步骤都可能需要回溯(比如清洗后发现数据质量问题,需要重新读取)

这张图我画了好几次才满意。你看,从数据读取到模型输入,每一步都环环相扣。而且,实际项目中经常需要回溯——比如清洗时发现数据格式有问题,就得回去重新读取。

3.6 本章小结

这一章的内容,说白了就是三件事:

  • NumPy:处理数值数组,做批量运算
  • Pandas:处理带标签的表格数据,尤其是时间序列
  • 数据清洗与插值:把脏数据变成可用的干净数据

我个人觉得,数据处理占整个电池建模工作量的60%以上。模型本身可能就几行代码,但数据准备能折腾好几天。所以,别嫌这些基础操作啰嗦——它们是你后续建模的基石。

下一章,咱们就开始搭建第一个等效电路模型了。到时候你会发现,数据准备得越充分,模型跑起来越顺手。


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