知识门户

返回

第7章 数据处理:pandas与numpy的测试数据流水线

第三篇:数据与展示

views | comments

第7章 数据处理:pandas与numpy的测试数据流水线#

本章核心理念:嵌入式工程师的数据不是教科书里的”Iris鸢尾花数据集”,而是从串口、网络、传感器中滚滚而来的真实工程日志——它们带着噪声、缺失值、乱序时间戳和各种”脏数据”。本章教你用 pandas 建立一条从”原始日志”到”清晰报表”的自动化流水线。


7.1 场景与痛点:海量测试日志的”数据沼泽”#

7.1.1 一个真实的周末噩梦#

周五下午五点半,你刚准备收工,老板走过来说:“下周一批量生产之前,把这批 500 块板子的温升测试数据整理成报表,每个板子算一下最高温度、平均温度、温升速率超标的次数……”

你看了看文件夹:

test_logs/
├── board_001_temp.csv
├── board_002_temp.csv
├── board_003_temp.csv
├── ...(共500个文件)
plaintext

每个文件长这样:

timestamp,sensor_id,temperature,voltage,status
2024-05-20 08:00:01.123,NTC_1,25.34,3.302,OK
2024-05-20 08:00:01.123,NTC_1,25.34,3.302,OK        ← 重复行
2024-05-20 08:00:01.456,NTC_2,25.67,3.298,OK
2024-05-20 08:00:01.789,NTC_1,,3.301,OK              ← 温度缺失
2024-05-20 08:00:02.001,NTC_3,1256.80,3.300,OK       ← 明显的传感器错误值
2024-05-20 08:00:02.345,NTC_1,25.89,3.302,
2024-05-20 08:00:02.890,NTC_2,26.01,3.297,OK
...(每个文件上万行)
csv

你的痛点清单

痛点具体表现
数据量大500个文件 × 每个文件2万行 = 1000万行,Excel 打开就卡死
格式不规范有重复行、空值、传感器异常尖峰、status列有时为空
需要聚合要按板子编号分组、按传感器ID分组、按时间段统计,再汇总成一张总表
时间对齐三个传感器的采样频率不同(100Hz、10Hz、1Hz),需要对齐到统一时间轴才能做关联分析
重复性劳动每批货都要跑一遍,下周又来一批,你不想再手动搞一次

如果你的处理逻辑是”打开Excel → 复制粘贴 → 手动筛选 → 求平均值”,那你这个周末就交代在这了。

7.1.2 我们需要什么样的工具?#

理想的工具应该能做到:

  1. 一行代码读取整个文件夹的几百个CSV,并自动合并为一个大表。
  2. 几秒钟内完成上万行数据的筛选、去重、缺失值填补
  3. 按任意维度(板子、传感器、时间段)分组聚合,一行代码出结果。
  4. 将不同采样率的时间序列对齐到统一时间轴
  5. 整个流程可复用——下周来新数据,改个文件夹路径,重新运行即可。

这个工具,就是 pandas


7.2 技术选型:原生 csv 模块 vs pandas#

7.2.1 Python 标准库 csv 模块能做什么?#

Python 自带的 csv 模块可以读写 CSV 文件,它轻量、零依赖,对于简单的逐行处理完全够用。

import csv

with open('board_001_temp.csv', 'r') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if row['status'] == 'OK':
            print(f"温度: {row['temperature']}°C")
python

csv 模块的局限:它只是一个”逐行读取器”,读出来的每一行都是字符串(str),你需要自己做类型转换、自己写循环筛选、自己实现分组聚合。当你的需求从”读出来看看”升级到”清洗、分析、统计”时,csv 模块就像用螺丝刀盖房子——工具本身没问题,但你得累死。

7.2.2 什么时候该用 pandas 这把”牛刀”?#

决策阈值:当你的数据处理需求命中以下任意一条时,请毫不犹豫地切换到 pandas:

条件说明
数据量 > 1万行pandas 基于 NumPy 的 C 语言底层实现,处理速度比纯 Python 循环快 10~100 倍
需要复杂筛选多条件组合查询(如”温度>30 且 电压<3.2 且 status为OK”),pandas 一行搞定
需要分组聚合”按板子编号分组,算每个板子的平均温度”——pandas 的 groupby() 是专门为此设计的
需要时间序列处理按时间窗口重采样、对齐不同频率的时间序列——pandas 内置强大的时间序列引擎
需要合并多表将500个CSV文件合并成一个大表,或者将两张表按某个键做 SQL 式的 JOIN
需要输出报表将结果导出为格式化的 Excel 文件(含多个 Sheet、合并单元格、条件格式)

7.2.3 pandas 与 NumPy 的关系#

很多初学者会困惑:pandas 和 NumPy 是什么关系?我需要先学 NumPy 吗?

一句话回答NumPy 是引擎,pandas 是车身。

维度NumPypandas
核心数据结构ndarray(多维数组)Series(一列)、DataFrame(一张表)
适合的数据类型同类型数值矩阵(全是浮点数、全是整数)异构表格数据(一列是时间戳、一列是浮点数、一列是字符串)
典型用途数学运算、矩阵计算、信号处理(配合 scipy)数据清洗、统计分析、报表生成
对嵌入式工程师用于处理 ADC 原始波形数据、做 FFT 频谱分析用于处理测试日志、生成自动化报表

实践建议:对于本章的数据处理任务,pandas 是主角,NumPy 是幕后英雄。你几乎不需要直接使用 NumPy 的 API——pandas 在底层已经帮你调用了。

7.2.4 安装#

# 推荐使用 pip 安装(pandas 内部已包含 numpy 依赖)
pip install pandas openpyxl
# openpyxl 是 pandas 读写 .xlsx 文件的后端引擎,强烈建议一并安装
bash

7.3 核心方法论:数据清洗三板斧与时间序列对齐#

7.3.1 第一板斧:去重——删除”幽灵行”#

问题来源:串口通信中偶尔出现的重复发送、数据采集软件的缓存回放、或者简单的人为复制粘贴错误。

识别重复的标准:你需要决定”什么算重复”——是所有列都一样算重复?还是某个关键列(如 timestamp + sensor_id)一样就算重复?

import pandas as pd

df = pd.read_csv('board_001_temp.csv')

# 方法1:删除所有列完全相同的行
df_unique = df.drop_duplicates()

# 方法2(更常用):删除关键字段相同的重复行,保留第一条
df_unique = df.drop_duplicates(subset=['timestamp', 'sensor_id'], keep='first')

# 查看删除了多少行
print(f"原始行数: {len(df)}, 去重后: {len(df_unique)}, 删除了 {len(df) - len(df_unique)} 行重复数据")
python

keep 参数的选择

  • keep='first':保留第一次出现的行(最常用,假设第一次是对的)。
  • keep='last':保留最后一次出现的行(适用于”后来的修正覆盖前面的”场景)。
  • keep=False:把所有重复的行都删掉(最严格,适用于你不想冒任何风险的场景)。

7.3.2 第二板斧:填补缺失值——别让”空洞”毁了你的统计#

缺失值的典型来源

  • 传感器通信瞬间丢失,某条数据的某个字段为空。
  • CSV 文件中本身就缺少某些列(有的行有 status 列,有的没有)。
  • 数据采集软件崩溃重启后,中间有一段时间没有记录。

pandas 读取时会自动将空值识别为 NaN(Not a Number),这是它的一大便利。

# 快速查看缺失值情况
print(df.isnull().sum())
# 输出示例:
# timestamp      0
# sensor_id      0
# temperature    3   ← 有3行温度缺失
# voltage        0
# status         1   ← 有1行状态缺失
python

填补策略的选择

策略方法适用场景
前向填充df['temperature'].fillna(method='ffill')传感器数据:温度不会突变,用前一个有效值填补(最常用)
后向填充df['temperature'].fillna(method='bfill')需要用后面的值回填(如记录开始时的缺失)
线性插值df['temperature'].interpolate(method='linear')需要更平滑的过渡,前后值之间按比例插值
固定值填充df['status'].fillna('UNKNOWN')分类字段,用一个明确的标记值代替(如 ‘UNKNOWN’、‘N/A’)
直接删除df.dropna(subset=['temperature'])缺失比例极低(<1%)且该字段是关键数据,不能容忍任何猜测
# 实战:对不同字段用不同策略
df['temperature'] = df['temperature'].interpolate(method='linear')  # 温度用线性插值
df['status'] = df['status'].fillna('UNKNOWN')                       # 状态用固定值
python

7.3.3 第三板斧:格式转换——让数据”说人话”#

CSV 文件读进来的所有东西都是字符串。pandas 会尝试自动推断类型,但你经常需要手动修正。

# 1. 时间列:转为 pandas 的 datetime 类型(这是后续一切时间操作的基础!)
df['timestamp'] = pd.to_datetime(df['timestamp'])

# 2. 数值列:强制转为浮点数,无法转换的标记为 NaN
df['temperature'] = pd.to_numeric(df['temperature'], errors='coerce')
df['voltage'] = pd.to_numeric(df['voltage'], errors='coerce')

# 3. 字符列:去除首尾空格(常见的"隐形杀手")
df['sensor_id'] = df['sensor_id'].str.strip()

# 4. 分类列:转为 category 类型(节省内存,加速 groupby)
df['status'] = df['status'].astype('category')
python

为什么要特别关注 to_datetime 因为一旦 timestamp 列被正确识别为 datetime 类型,pandas 就会为你解锁一整套时间序列操作能力:按小时/天/月聚合、时间窗口滚动计算、时间差计算、时区转换……这些在下一节的”时间序列对齐”中至关重要。

7.3.4 去除异常值:给数据”修剪枯枝”#

工业传感器数据中,偶尔会出现明显不合理的尖峰。比如室温测试中突然出现一个 1256.80°C 的读数,那大概率是传感器通信错误,而不是真的着火了。

7.3.5 进阶:时间序列重采样与对齐——不同频率传感器的”同步合奏”#

这是嵌入式数据处理中最具特色、也最容易踩坑的环节。

为什么需要对齐?#

一个真实的测试场景:你的待测设备上贴了三种传感器:

传感器采样频率数据含义
三轴加速度计100 Hz(每10ms一条)振动分析
热电偶温度10 Hz(每100ms一条)温升曲线
电池电压1 Hz(每1秒一条)供电稳定性

它们各自输出的 CSV 文件,时间戳不可能完全对齐——加速度计可能在 08:00:00.010 采了一个点,温度传感器在 08:00:00.095 采了一个点,电压在 08:00:01.002 采了一个点。

如果你想分析”当电压骤降时,加速度和温度有什么变化”,就必须先把三个数据源对齐到同一个时间轴上。

策略一:降采样(高频率 → 低频率)#

把所有数据统一到最低频率(比如 1Hz),对高频数据做聚合。

# 假设 accel_df 是加速度数据,已设置 timestamp 为索引
# 将100Hz数据按1秒窗口聚合(取均值、最大值、标准差)
accel_resampled = accel_df.resample('1s').agg({
    'accel_x': ['mean', 'max', 'std'],
    'accel_y': ['mean', 'max', 'std'],
    'accel_z': ['mean', 'max', 'std']
})
# 结果:每秒一行,包含该秒内加速度的均值、峰值、波动

temp_resampled = temp_df.resample('1s').mean()   # 温度取1秒均值
voltage_1hz = voltage_df                              # 电压本身就是1Hz,无需处理
python

策略二:merge_asof——最近时间匹配#

当两个数据源的采样时间不完全一致时,merge_asof 是 pandas 提供的”杀手级”功能。它为左表中的每个时间戳,在右表中找到时间上最接近的那条记录进行匹配。

输出示意

                  timestamp   accel_x   accel_y   accel_z  temperature  voltage
0 2024-05-20 08:00:00.000  0.342156  0.123456  9.812345          NaN      NaN
1 2024-05-20 08:00:00.010  0.567890  0.234567  9.923456          NaN      NaN
2 2024-05-20 08:00:00.020 -0.123456 -0.345678  9.678901          NaN      NaN
...
9 2024-05-20 08:00:00.090  0.789012  0.456789 10.012345         25.25      NaN
plaintext

注意前几行的 temperaturevoltageNaN,因为加速度计最早的时间点(08:00:00.000)早于温度传感器的起始时间(08:00:00.050),tolerance 限制了匹配范围。

策略三:插值补齐——填补匹配后的空洞#

merge_asof 之后,可能仍有一些 NaN(时间差距超出 tolerance,或者刚好卡在两个采样点之间)。这时可以对合并后的数据做一次插值。

# 对合并后的数值列做线性插值
numeric_cols = ['temperature', 'voltage']
merged[numeric_cols] = merged[numeric_cols].interpolate(method='linear')

# 首尾的 NaN 可能插值不了(没有参考点),用前向/后向填充兜底
merged[numeric_cols] = merged[numeric_cols].fillna(method='ffill').fillna(method='bfill')

print(merged.isnull().sum())  # 确认没有缺失值了
python

完整对齐流程总结


7.4 核心实战:分组聚合生成自动化测试报表#

现在,让我们回到 7.1 节的那个”周末噩梦”场景,用 pandas 建立一条完整的数据处理流水线。

7.4.1 目标#

输入:一个文件夹,里面放着 500 块板子的温升测试 CSV 文件。

输出:一个 Excel 报表,包含:

  1. 汇总表:每块板子一行,列出最高温度、平均温度、温升速率超标次数、测试结论(PASS/FAIL)。
  2. 异常明细表:所有温度异常数据点的详细记录,方便追溯。

7.4.2 完整代码#

7.4.3 代码解读与设计决策#

1. 为什么用函数拆分而不是写成一个大函数?

每个函数职责单一,方便:

  • 单独测试:你可以只调用 clean_data() 看看清洗效果,不影响其他步骤。
  • 替换策略:如果换了一批数据,清洗规则不同,只需替换 clean_data() 函数。
  • AI 协作:把某个函数单独喂给 AI 审查或优化,上下文清晰。

2. 配置区为什么放在文件顶部?

这是工程脚本的黄金法则——所有需要根据项目调整的参数集中放在最前面,用大写字母常量命名。下次换一批数据、换一个阈值,你只需要改最上面的几行,而不是在代码深处翻找。

3. groupby().agg() vs groupby().apply()

  • agg() 适合内置聚合函数(max, mean, std, count 等),速度快。
  • apply() 适合自定义复杂逻辑(如温升速率需要同时用到温度和时间的差分),更灵活但稍慢。

在我们的代码中,简单统计用 agg(),温升速率计算用 apply(),各取所长。


7.5 AI协作指南:用自然语言描述数据特征与期望输出#

7.5.1 核心理念:把你的数据”翻译”给AI听#

pandas 的 API 非常丰富,同一个需求可能有十种写法。AI 的优势在于:你用自然语言描述”我有什么数据、我想得到什么结果”,它就能帮你生成对应的 pandas 代码链。

但 AI 生成的代码能否直接用,取决于你给它的上下文质量。

7.5.2 万能 Prompt 模板#

7.5.3 AI 审查重点清单#

AI 生成的 pandas 代码,重点审查以下容易出错的地方:

审查项常见问题检查方法
类型转换没有调用 pd.to_datetime()pd.to_numeric(),导致后续时间操作或数值比较失败在转换后加 print(df.dtypes) 确认
链式赋值陷阱df[condition]['col'] = value 不会修改原始 DataFrame(SettingWithCopyWarning)使用 .loc[] 进行赋值:df.loc[condition, 'col'] = value
缺失值处理顺序先插值再去重,可能导致插值出”虚假数据”严格按 去重 → 类型转换 → 缺失处理 → 异常过滤 的顺序
时间索引resample() 要求索引是 DatetimeIndex,AI 可能忘记 set_index('timestamp')检查 resample 前的索引类型
内存爆炸读取500个CSV时全部存入列表再 concat,如果每个文件100MB 就会 OOM超大数据量时分批读取、处理后只保留结果
groupby 排序groupby() 默认会排序,如果不需要排序(性能考虑),加 sort=False根据实际需求决定

7.5.4 进阶 Prompt:让AI帮你做数据探索#

当你拿到一份陌生的数据集,不确定里面有什么”坑”时,可以让 AI 帮你写一个数据探索脚本:

我有一份CSV文件: data.csv
请帮我写一个数据探索脚本,完成以下任务:
1. 读取文件,输出总行数、列名、各列数据类型
2. 输出每列的缺失值数量和缺失比例
3. 对数值列输出统计摘要(均值、标准差、最小值、25%/50%/75%分位数、最大值)
4. 对时间列输出最早和最晚时间戳,以及总时间跨度
5. 对分类列输出每个类别的出现次数
6. 检测是否存在完全重复的行,输出重复行数
7. 对数值列检测异常值(使用IQR方法),输出异常值数量
8. 将以上所有信息输出为一个结构清晰的文本报告

请使用 pandas,代码中加入中文注释。
plaintext

这个探索脚本跑一遍,你对数据的”全貌”就有数了,后续的数据清洗和分析就有了方向。


本章小结#

你学到了什么解决了什么问题
pandas 基础读写与类型转换告别 Excel 打不开大文件的痛苦
数据清洗三板斧(去重、填补、转换)让”脏数据”变得干净可用
异常值检测与处理自动识别传感器错误,而不是肉眼逐行检查
时间序列重采样与 merge_asof 对齐让不同频率的传感器数据在统一时间轴上”合奏”
groupby 分组聚合500块板子的数据,一行代码算出每块的统计指标
自动化报表输出跑一个脚本就出 Excel 报表,老板周一就能看到
AI 协作 Prompt 模板用自然语言描述需求,让 AI 帮你生成和审查 pandas 代码

下一章预告:数据处理完了,但”最高温度85.3°C”这样的数字远不如一张漂亮的温升曲线图来得直观。第8章我们将学习如何用 matplotlib 和 Plotly,把枯燥的数字变成工程师一看就懂的专业图表

Comment seems to stuck. Try to refresh?✨