从数据到洞察:用Python深度解析XJTU电池数据集,解锁电压、电流与温度曲线的秘密
如果你手头有一份来自真实世界的电池循环测试数据,比如XJTU电池数据集,第一反应是什么?是直接打开Excel画几条折线图,还是尝试用代码去挖掘更深层的规律?我刚开始接触这个数据集时,也经历过一段“数据在手,无从下手”的时期。这个数据集包含了55个18650锂电池在不同充放电策略下的完整生命周期数据,采样频率高达1Hz,信息量巨大。但原始数据是.mat格式,结构嵌套复杂,直接处理起来并不直观。今天,我想和你分享的,不仅仅是如何用Python画出电压、电流、温度曲线,而是如何构建一套可复用、可扩展的分析框架,将原始数据转化为清晰的物理洞察和工程判断。无论你是从事电池健康状态(SOH)估计、故障诊断,还是单纯对数据科学在能源领域的应用感兴趣,这套方法都能让你快速上手,并具备处理类似时序数据的通用能力。
1. 数据基石:构建一个灵活高效的电池数据解析器
拿到XJTU数据集,第一步不是急着画图,而是理解数据结构并构建一个可靠的读取接口。原始数据以MATLAB的.mat文件存储,每个文件对应一个电池,内部包含了data和summary两个关键结构。data是一个复杂的嵌套数组,存储了每个循环周期(cycle)下多个通道(电压、电流、温度、时间等)的时序数据;summary则包含了电池的元信息,如循环寿命、容量序列等。直接操作这些原始结构不仅代码冗长,而且容易出错。
我的做法是封装一个Battery类,它像是一个智能的数据管家,将复杂的底层数据访问抽象为清晰的方法。这个类的核心价值在于统一数据接口和提供计算衍生指标的能力。
import numpy as np
from scipy.io import loadmat
from scipy import interpolate
import functools
class Battery:
"""
一个用于解析和操作XJTU电池数据集的类。
它将复杂的.mat文件结构封装成易于使用的属性和方法。
"""
def __init__(self, filepath):
"""
初始化,加载数据并解析基本信息。
参数:
filepath (str): .mat数据文件的路径。
"""
mat_data = loadmat(filepath)
self.raw_data = mat_data['data'] # 原始时序数据
self.filename = filepath.split('/')[-1].split('.')[0] # 电池标识
self.summary = mat_data['summary']
# 从summary中提取关键元数据
self.cycle_life = self.summary[0][0][8][0][0] # 总循环次数
self.description = self.summary[0][0][9][0] # 实验描述
self.capacity_history = self.summary[0][0][1].flatten() # 容量衰减历史
# 定义数据通道名称,方便索引
self.channel_names = [
'system_time', # 系统时间戳
'relative_time_min', # 相对时间(分钟)
'voltage_V', # 电压 (V)
'current_A', # 电流 (A)
'capacity_Ah', # 累计容量 (Ah)
'power_Wh', # 功率 (Wh)
'temperature_C', # 温度 (°C)
'description' # 循环阶段描述
]
print(f"已加载电池: {self.filename}")
print(f" 循环寿命: {self.cycle_life} cycles")
print(f" 实验描述: {self.description}")
这个初始化方法完成了数据的加载和基本信息的提取。但更重要的是,我们需要设计方法来获取特定循环、特定阶段的数据。例如,一个完整的充放电循环通常包含四个阶段:恒流充电(CC)、静置(Rest)、恒流放电(DC)、再次静置(Rest)。数据集中的description字段标记了这些阶段的起点。
def get_cycle_data(self, cycle_num, channel):
"""
获取指定循环周期、指定通道的完整时序数据。
参数:
cycle_num (int): 循环序号(从1开始)。
channel (str/int): 通道名称或索引。
返回:
np.ndarray: 该通道的时序数据数组。
"""
if isinstance(channel, str):
channel_idx = self.channel_names.index(channel)
else:
channel_idx = channel
if cycle_num > self.raw_data.shape[1] or cycle_num < 1:
raise ValueError(f"循环序号应在1到{self.raw_data.shape[1]}之间")
# 提取数据并展平为一维数组
data_array = self.raw_data[0][cycle_num-1][channel_idx]
if channel_idx == 7: # description字段是字符串
return data_array[0]
else:
return data_array.flatten()
def get_phase_data(self, cycle_num, channel, phase=1):
"""
获取指定循环、指定通道、特定充放电阶段的数据。
阶段定义: 1-充电, 2-第一次静置, 3-放电, 4-第二次静置。
对于Batch-6(卫星模拟)等只有3个阶段的数据,阶段3为放电。
参数:
cycle_num (int): 循环序号。
channel (str/int): 通道名称或索引。
phase (int): 阶段序号。
返回:
np.ndarray: 该阶段的数据片段。
"""
full_data = self.get_cycle_data(cycle_num, channel)
time_rel = self.get_cycle_data(cycle_num, 'relative_time_min')
# 找到相对时间为0的点,这些点标记了阶段的开始
phase_start_indices = np.where(time_rel == 0)[0]
# 在数组末尾添加一个“虚拟”的结束索引
phase_start_indices = np.append(phase_start_indices, len(full_data))
start_idx = phase_start_indices[phase-1]
end_idx = phase_start_indices[phase]
return full_data[start_idx:end_idx]
提示:
get_phase_data方法是后续分析的关键。它利用relative_time_min通道中为零的点作为阶段分割标志,能精准地剥离出充电、静置、放电等不同过程的数据,为分阶段可视化与分析奠定了基础。
有了这个基础类,我们就可以像使用字典一样方便地访问数据了。例如,要获取第50次循环的完整电压曲线,只需battery.get_cycle_data(50, 'voltage_V');要获取其充电阶段的电流,则是battery.get_phase_data(50, 'current_A', phase=1)。这种封装将数据访问的复杂性隐藏起来,让我们能更专注于数据本身的意义。
2. 可视化实战:绘制多维时间序列与退化轨迹
数据准备好了,接下来就是让数据“说话”的时候。可视化不仅仅是画图,更是探索数据模式、验证假设、发现异常的过程。对于电池数据,我们至少需要从三个层面进行可视化:单次循环的微观行为、特定阶段的细节特征以及全生命周期的宏观演化。
2.1 绘制单次循环的完整画像
首先,让我们看看一个完整的电池循环周期内,电压、电流和温度是如何协同变化的。这能帮助我们直观理解电池的工作状态。

&spm=1001.2101.3001.5002&articleId=152908197&d=1&t=3&u=88aa2ae569ee4f0baeaaec00e4166d55)
6727

被折叠的 条评论
为什么被折叠?



