2. 食品质量数据特征与采集

控制图不是“把数字画成折线”这么简单。它能否给出可信的报警,首先取决于我们拿到的是什么样的数据。食品质量数据有几个非常鲜明的特征:它天然按批次聚集,按时间排列,常常以子组形式出现,并且大量质量指标是缺陷数或不合格品数这类计数型数据。理解这四类特征,是后续选择 X̄-R 图、p 图、c 图还是 u 图的前提。

四类核心数据形态

批次数据是最基本的组织单位。一个批次通常对应一次投料、一个生产班次或一锅/一罐产品,例如“2024-06-12 A班 灌装线2 批次B24061202”。批次是追溯的锚点,也是控制图分组的天然边界。跨批次混合数据而不加区分,会把批次间的正常差异误当成过程波动。

时间序列数据强调顺序。同一指标按采样时间排列,形成有序序列,控制图的判异规则(如连续7点上升、链状分布)都依赖这个顺序。因此时间戳必须可解析、无重复、无乱序,且采样间隔要记录清楚——是每30分钟一次,还是每批次一次,直接决定控制图横轴的含义。

子组数据是计量型控制图的关键。子组指在同一相对稳定的条件下连续抽取的一组样本,例如每批随机取5袋测净含量,这5个值构成一个子组,计算子组均值 X̄ 和极差 R。子组大小 n 应保持一致,通常取 3~5;n 变化会让 R 图的控制限失去可比性。

缺陷数/不合格品数属于计数型数据。前者如每袋产品中的异物点数、每批的微生物超标样品数(c 图、u 图),后者如抽检100袋中有几袋封口不良(p 图、np 图)。这类数据必须同时记录“缺陷数”和“检验单位数”,否则无法计算不合格率。

食品质量数据:来源 → 形态 → 清洗 → 控制图选型 实验室 LIMS 理化 / 微生物结果 CSV、Excel、PDF报告 生产 MES 在线称重 / 封口 / 温度 数据库表、接口导出 四类数据形态 批次:B24061202 / 班次 时间序列:采样时刻有序 子组:每批取 n=5 袋 缺陷数 / 不合格品数 清洗要点 ① 统一单位(g / mg / %) ② 时间戳解析、去重、排序 ③ 缺失值标记,不擅自补零 ④ 剔除“<检出限”等文本值 ⑤ 子组大小 n 一致性校验 ⑥ 保留批次号与检验单位数 清洗后按数据类型选择控制图 计量型 · 子组 X̄-R / X̄-S 图 计量型 · 单值 I-MR 图 不合格品数 p 图 / np 图 缺陷数 c 图 / u 图

LIMS 与 MES 导出数据的常见格式

实验室 LIMS 导出的数据多为“宽表 + 文本结果”的混合体。典型字段包括:样品编号、批次号、采样时间、检验项目(如水分、菌落总数)、结果值、单位、检出限、判定结论。麻烦在于结果列常混有“<10”“未检出”“ND”这类文本,单位也可能同一列里既有 g/100g 又有 %。生产 MES 的数据则更接近“长表”:时间戳、设备号、批次号、测点编码、测量值,一行一个测点读数,数据量大、采样频率高,但往往缺少检验单位数这类统计口径信息。

两种来源的对接点通常是批次号。LIMS 的样品要能关联到 MES 的批次,才能把“实验室测的水分”和“生产线记录的烘干温度”放在同一批次语境下分析。若批次号编码规则不一致(一边是 B24061202,一边是 20240612-02),就必须先建立映射,否则后续合并会大面积失配。

清洗的关键动作与判断依据

清洗不是“删掉空行”那么简单,核心是保证统计口径一致。第一,统一单位并换算到同一量纲,例如把水分统一为 g/100g。第二,处理检出限:对于“<10”这类删失数据,不能直接当 10 也不能当 0,通常按检出限的一半代入并单独标记,供后续敏感性分析。第三,时间戳统一为同一时区并排序,重复记录按样品编号去重。第四,校验子组完整性:若某批应取 5 个样却只有 3 个有效值,该子组应标记为不完整,而不是硬凑。

下面是一段可直接复用的清洗骨架,演示如何把 LIMS 导出的宽表整理成“批次—子组—均值—极差”的结构:

import pandas as pd
import numpy as np

# 假设 lims.csv 含: 样品编号, 批次号, 采样时间, 项目, 结果, 单位
df = pd.read_csv("lims.csv", dtype=str)
df["采样时间"] = pd.to_datetime(df["采样时间"], errors="coerce")

# 1) 只保留目标项目,统一单位到 g/100g
df = df[df["项目"] == "水分"].copy()
df["结果"] = df["结果"].str.replace("<", "", regex=False)
df["结果"] = pd.to_numeric(df["结果"], errors="coerce")
df.loc[df["单位"] == "%", "结果"] = df["结果"] * 1.0   # % 与 g/100g 数值等价

# 2) 丢弃无法解析的结果,但保留记录用于追溯
bad = df[df["结果"].isna()]
df = df.dropna(subset=["结果", "采样时间"])

# 3) 按批次聚合成子组,计算均值与极差
grp = df.groupby("批次号")["结果"]
sub = grp.agg(n="count", Xbar="mean", R=lambda s: s.max() - s.min())
sub = sub[sub["n"] == 5]          # 只保留完整子组 n=5
print(sub.head())

运行后得到的 sub 表,每行就是一个批次对应的子组统计量,n 列用于确认子组完整性,Xbar 与 R 可直接送入 X̄-R 控制图。若某批次 n 不等于 5,会被过滤掉并在 bad 中留痕,便于回溯是采样遗漏还是录入错误。

常见陷阱:把不同班次、不同产线的数据混在同一张控制图里。控制图要求“过程处于同一稳定条件”,跨产线混合会人为放大波动,导致控制限过宽、真实异常被掩盖。正确做法是先按产线/班次分层,再分别建图。
判断数据是否“可上图”的三个快速检查:① 每个子组的 n 是否一致;② 时间戳是否严格递增且无重复;③ 计数型数据是否同时具备缺陷数与检验单位数。三项都通过,再进入控制图计算,能避免大量返工。