3. 测试数据接入与统一数据模型

测试现场的数据格式往往比想象中更杂:台架采集软件导出 CSV,人工记录整理成 Excel,MATLAB 仿真或后处理结果保存为 MAT,NI 采集设备则常以 TDMS 落盘。如果每个分析脚本都针对一种格式单独写读取逻辑,后续做批量分析、自动报告和跨试验对比时就会反复重写。本节要解决的核心问题,是建立一个“多格式读取 → 统一表结构”的接入层,让上层分析代码只面对一种数据形态。

统一数据模型:先定“表”,再谈“读”

统一表结构不是把所有数据塞进一张大表,而是约定一套稳定的列语义。对测试数据而言,最常用的是长表(long table)结构,每行代表一个采样点或一条记录,至少包含以下列:

  • time:时间戳或相对时间,单位统一为秒(s),作为横轴基准;
  • signal:信号名称,字符串,用于区分同一时刻的不同通道;
  • value:该信号在该时刻的数值,统一为 double;
  • unit:物理单位,如 V、℃、rpm,便于报告标注;
  • source:数据来源文件或设备标识,用于追溯;
  • meta:可选,存放采样率、量程、试验编号等元信息。

这种结构的好处是:无论原始数据是宽表(每列一个信号)还是多通道二进制,转换后都能用同一套筛选、重采样、绘图和统计代码处理。若某些信号采样率不同,长表允许各信号拥有独立时间轴,后续按需对齐,而不是在读取阶段强行插值。

四类格式的读取要点

CSV 是最简单也最容易出问题的格式。常见坑包括:分隔符不统一(逗号、分号、制表符)、小数点用逗号、首行有中文注释、时间列是“2024/06/01 10:23:45.123”这类字符串。读取时建议显式指定参数,而不是依赖自动探测:

opts = detectImportOptions('run01.csv', ...
    'Delimiter', ',', ...
    'DecimalSeparator', '.', ...
    'VariableNamingRule', 'preserve');
opts = setvartype(opts, 'Time', 'datetime');
T = readtable('run01.csv', opts);

读入后先检查 height(T)、summary(T) 和缺失值比例,再决定是否剔除无效行。时间列若为字符串,用 datetime 解析并转换为相对秒:t = seconds(T.Time - T.Time(1))。

Excel 的复杂性在于多工作表、合并单元格和公式。读取时用 readtable 指定 Sheet 和 Range,避免把表头说明行当成数据。若一个文件里每个 sheet 是一个工况,可用循环批量读取并追加 source 列。注意 Excel 的日期序列号与 MATLAB datetime 的转换,建议统一用 readcell 先看原始内容再决定解析方式。

MAT 文件通常已经是 MATLAB 变量,读取用 load 或 matfile。关键不是“读”,而是识别变量结构:可能是 data.time 与 data.signals 的嵌套结构,也可能是 Simulink 的 logsout 对象。接入层需要写一个适配函数,把已知字段映射到统一列。对于大文件,用 matfile 按需读取变量,避免一次性载入内存。

TDMS 是 NI 的层次化二进制格式,包含文件、组、通道三级结构。MATLAB 没有内置读取函数,通常借助 tdmsreader 或第三方包。读取后得到的是各通道的属性与数据数组,需要把通道名、单位、采样率提取出来,再展开为长表。TDMS 的时间信息常以“起始时间 + 采样间隔”给出,转换时按 t = t0 + (0:N-1)/fs 生成时间列。

多格式测试数据接入与统一表结构转换 CSV 分隔符/时间字符串 Excel 多Sheet/合并单元格 MAT 嵌套变量/大文件 TDMS 文件/组/通道三级 格式适配层 readCSV / readExcel readMAT / readTDMS 字段映射与类型转换 时间轴归一化 缺失值标记 统一长表结构 time signal value unit 0.000 Temp_A 25.3 ℃ 0.001 Temp_A 25.4 ℃ 0.000 RPM 1500 rpm 0.001 RPM 1502 rpm … 每行一个采样点,signal 区分通道 source 列记录来源文件,便于追溯 meta 列存放采样率、量程等元信息 转换 上层分析:筛选 / 重采样 / 绘图 / 统计 / 自动报告

转换实现:一个可复用的接入函数

把上述逻辑封装成一个入口函数 loadTestData(filePath),根据扩展名分派到具体读取器,再统一调用 toLongTable 完成列映射。核心转换代码大致如下:

function T = toLongTable(time, signals, units, source)
% time: N×1 double (s)
% signals: N×M double
% units: 1×M cellstr
    n = numel(time);
    m = numel(units);
    T = table();
    for k = 1:m
        Tk = table(repmat(time,1,1), ...
            repmat(string(units{k}), n, 1), ...
            signals(:,k), ...
            repmat(string(units{k}), n, 1), ...
            repmat(string(source), n, 1), ...
            'VariableNames', {'time','signal','value','unit','source'});
        T = [T; Tk];
    end
end

实际工程中还要处理三个细节:一是时间列若为 datetime,先转 posixtime 再减去首值;二是不同信号采样率不同时,保留各自时间列,不强行对齐;三是缺失值用 NaN 标记,而不是删除行,以便后续统计缺失率。

接入质量检查

转换完成后,建议做一次快速体检,判断数据是否可用:

检查项判断依据异常处理
行数是否合理行数 ≈ 信号数 × 采样点数检查是否漏读通道或重复追加
时间单调性all(diff(time) > 0)排序或剔除重复时间戳
缺失值比例各信号 NaN 占比 < 5%标记并记录,报告中说明
单位一致性同信号 unit 唯一统一换算,如 mV → V
采样间隔稳定性std(diff(time)) 远小于均值检查丢包或时间戳异常
建议把每次接入的源文件路径、行数、信号列表、缺失率写入一个 ingest_log 表,随分析结果一起保存。这样当报告数据异常时,可以快速定位是原始数据问题还是转换逻辑问题。