3. HIL测试数据预处理实战

HIL台架采集到的原始数据几乎不可能直接用于分析。以某新能源整车HIL测试为例,一次30分钟的驱动工况测试,dSPACE实时系统以1 ms步长记录电机转速、扭矩、母线电压、电流、冷却液温度、CAN报文中的挡位与踏板开度等信号,单次测试即可产生数十万行、上百列的数据。这些数据在进入模型修正环节之前,必须经过缺失值处理、异常值检测、时间同步、重采样与滤波五道工序。本节以一份模拟的HIL数据集为对象,完整走通清洗与对齐流程。

一、先看清数据长什么样

假设我们从HIL台架导出的原始文件为 hil_run_003.csv,包含以下列:time_ms(实时系统时间戳)、n_motor(电机转速,rpm)、T_motor(电机扭矩,N·m)、U_dc(母线电压,V)、I_dc(母线电流,A)、T_coolant(冷却液温度,℃)、gear(挡位,CAN信号)、pedal(踏板开度,%)。其中 n_motor、T_motor、U_dc、I_dc 来自实时模型IO,采样周期1 ms;gear、pedal 来自CAN总线,周期10 ms;T_coolant 来自温度采集模块,周期100 ms。三类信号周期不同,这是后续时间同步与重采样的根源。

用Python载入后先做一次概览:

import pandas as pd
df = pd.read_csv("hil_run_003.csv")
print(df.shape)          # (1800000, 8)
print(df.isna().sum())   # 各列缺失数量
print(df.describe())     # 均值、极值、分位数

假设概览结果显示:n_motor 缺失 312 行,T_motor 缺失 87 行,U_dc 缺失 45 行,gear 缺失 1200 行,T_coolant 缺失 0 行。同时 n_motor 最大值出现 12000 rpm,而该电机额定转速为 9000 rpm,说明存在异常值。这些数字都是为教学构造的示例,实际项目中需以真实统计为准。

二、缺失值处理:先判断缺失类型,再选策略

缺失值不能一律用均值填充。要先区分三种情况:随机缺失(如CAN偶发丢帧)、连续缺失(如传感器断线一段时间)、结构性缺失(如某工况下信号本就不该有值)。对HIL数据,常用策略如下:

  • 连续缺失长度 ≤ 3 个采样点:用线性插值 df.interpolate(method="linear", limit=3),对转速、扭矩这类连续物理量误差可接受。
  • 连续缺失长度 > 3 个采样点:标记为无效段,不插值,后续分析时按无效区间剔除,避免用插值伪造动态过程。
  • CAN信号 gear 这类离散量:用前向填充 df["gear"].ffill(),因为挡位在换挡前保持不变,前向填充符合物理逻辑。
  • 温度 T_coolant 变化慢,若缺失可用前向填充或滑动均值填充。

执行后应再次检查 df.isna().sum(),确认剩余缺失均为已标记的无效段,而不是被静默填掉。

三、异常值检测:物理边界 + 统计方法双管齐下

异常值检测不能只看3σ。HIL数据中,传感器量程、执行器物理极限、模型输出限幅都是硬边界。建议先用物理边界做粗筛,再用统计方法做细筛。

物理边界示例:n_motor ∈ [0, 9000] rpm,T_motor ∈ [-300, 300] N·m,U_dc ∈ [200, 500] V,pedal ∈ [0, 100] %。超出边界的点直接判为异常。

统计方法可用滑动窗口的3σ或IQR。以 n_motor 为例,取窗口 1000 点,计算窗口内均值 μ 与标准差 σ,若某点 |x−μ| > 3σ 则标记。注意:电机急加速时转速变化率大,3σ 可能误判,因此对转速建议改用变化率约束:相邻两点转速差超过 500 rpm/ms 才判异常。

# 物理边界粗筛
mask_phys = (df.n_motor < 0) | (df.n_motor > 9000)
# 变化率约束
d_n = df.n_motor.diff().abs()
mask_rate = d_n > 500
df.loc[mask_phys | mask_rate, "n_motor"] = pd.NA

异常值处理方式:单点异常用前后均值替换;连续异常段整段置为无效,不参与后续模型修正。

四、时间同步:把不同周期的信号对齐到同一时间轴

HIL数据的时间同步包含两层:同一控制器内不同任务周期的信号对齐,以及不同控制器/总线之间的时钟对齐。本例中,1 ms、10 ms、100 ms 三类信号必须统一到同一时间基准。

做法是:以最快周期 1 ms 为基准时间轴,将 10 ms 和 100 ms 信号通过“零阶保持”扩展到 1 ms 网格上。零阶保持的含义是:在下一个采样点到来之前,保持上一个值不变。这符合CAN信号在两次报文之间的真实行为。

t_base = df.time_ms  # 1 ms 网格
df = df.set_index("time_ms")
df_1ms = df.reindex(t_base, method="ffill")  # 零阶保持

如果存在两个控制器时钟漂移,还需用共同触发信号(如同时出现的制动开关信号)做互相关,估计时延并补偿。示例中假设两路时钟已由HIL统一时基同步,仅做周期对齐。

五、重采样与滤波:降采样前必须先抗混叠

1 ms 数据量太大,模型修正通常只需要 10 ms 或 100 ms 数据。降采样不能直接抽点,否则高频噪声会混叠到低频。正确顺序是:先低通滤波,再降采样。

对转速、扭矩、电压、电流,截止频率取 50 Hz 左右(高于主要动态,低于奈奎斯特频率的一半)。用巴特沃斯低通滤波器:

from scipy.signal import butter, filtfilt
b, a = butter(4, 50/(1000/2), btype="low")
df["n_motor_f"] = filtfilt(b, a, df["n_motor"].ffill())
df_10ms = df.iloc[::10].copy()  # 1 ms → 10 ms

滤波后再降采样,得到 10 ms 网格数据。温度信号本身变化慢,可直接从 100 ms 重采样到 10 ms,用线性插值即可,无需额外滤波。

滤波会引入相位滞后,filtfilt 通过正反两次滤波实现零相位,适合离线分析;若做在线处理,需改用单向滤波并补偿时延。

六、完整清洗流程与结果检查

把上述步骤串成一条可复用的处理链:读取原始CSV → 缺失值分类处理 → 物理边界与变化率异常检测 → 统一到1 ms基准 → 低通滤波 → 降采样到10 ms → 输出清洗后数据集。每一步都保留处理日志,记录被修改的行号与原因。

清洗完成后,用三项检查判断是否达标:一是时间轴严格等间隔,无重复、无跳变;二是各信号在物理边界内,且相邻点变化率合理;三是关键工况段(如急加速、制动)的波形与原始数据趋势一致,没有因滤波或插值产生虚假动态。若发现某段转速在滤波后出现明显滞后于扭矩,说明截止频率选得过低,需要回调。

HIL数据预处理五道工序与信号流 原始HIL数据 1ms / 10ms / 100ms 8列 · 180万行 ① 缺失值处理 线性插值 ≤3点 前向填充离散量 ② 异常值检测 物理边界 + 变化率 3σ / IQR 细筛 ③ 时间同步 统一到1ms基准 零阶保持对齐 ④ 低通滤波 50Hz 巴特沃斯 零相位 filtfilt ⑤ 重采样 1ms → 10ms 先滤波后抽点 清洗后数据集 10ms等间隔 物理边界内 三类信号周期与处理策略对照 信号 原始周期 缺失/异常策略 滤波与重采样 n_motor / T_motor 1 ms 插值≤3点,变化率>500判异常 50Hz低通 → 10ms gear / pedal 10 ms 前向填充,边界[0,100] 零阶保持到1ms → 10ms T_coolant 100 ms 前向填充,慢变量 线性插值到10ms

经过上述流程,原始 180 万行、8 列的数据被整理为 18 万行、10 ms 等间隔的清洗数据集,缺失与异常被显式标记或修复,三类周期信号统一到同一时间轴,高频噪声在降采样前被抑制。这份数据即可作为后续模型修正的输入。实际项目中,建议把每一步的参数(插值上限、物理边界、滤波截止频率、目标采样周期)写入配置文件,便于复现和审计。