数据负责人收到文件时,最醒目的是整整齐齐的 1,200 行。表头完整,营养值都有小数,总分也都落在 0—10 之间。这样的表很容易让人马上计算平均数,仿佛清洗只剩删除几个异常值、填补几处空白。

真正需要先问的却是:1,200 个早餐编号是否都不重复,240 家店是否各有五份,空白是漏填还是按照设计不适用,总分能否由公开的营养值重新算出来。数据清洗不是把表格修成整齐的样子,而是弄清每种异常怎样产生,并让保留、修改或排除都有可复查的依据。

这份数据由程序模拟生成,固定了随机种子,使别人重新运行时可以得到同一批记录。所有城市、店铺、早餐和结果只用于演示科研方法,不代表现实中的中式早餐,也不能用来提出饮食建议。模拟并没有降低清洗要求;相反,它让我们能够准确看到一个错误怎样从生成规则进入结果。

异常首先是一条需要解释的证据

结构核对显示,文件有 1,200 个不重复的 combo_id、240 个 shop_id,每家店正好五行,六个城市各 200 行。早餐摊点、独立门店、连锁门店和食堂窗口分别有 300、420、270 和 210 行,与抽样计划一致。这一步回答的不是早餐营养如何,而是当前文件是否仍是原本准备分析的那一份。

总行数对得上,并不保证内部结构正确。漏掉一家店的一份早餐,再重复另一家店的一行,总数仍然是 1,200。某店只有四行,也可能不是上传失败,而是第五份后来被确认不符合早餐组合定义。重复、漏同步和排除会留下相似的表面现象,处理前必须回到编号和流转记录。

关键身份和营养字段在模拟库中没有普通缺失,但 90 份早餐的份量没有现场实测,占 7.5%;148 份配方使用参照估算,占 12.3%。它们不是应该立即删除的“坏数据”,而是测量来源不同的数据。正确做法是保留质量标记,在主要分析以后排除这些记录重算,看看结论是否明显改变。

120 份早餐进入参照测量子样本,其余 1,080 份在 reference_protein_g 和 reference_sodium_mg 中本来就不应有值。把这 1,080 个空白统计成“参照值缺失率 90%”,会把研究设计误报成数据事故。相反,如果 120 份复核早餐里有一份参照值为空,才需要追查发生了什么。清洗不能只识别空格,还要理解空格为何存在。

极端值也不是自动删除命令。模拟库最高钠为 1,862.2 mg,最高能量为 841.5 kcal,都远离样本中间位置,却仍在生成规则允许范围内,编号、单位和来源没有冲突,因此保留。若仅凭“看起来不像正常早餐”删掉,研究者会在没有现场证据的情况下主动缩窄分布。图上的孤立点首先要求核对,而不是要求消失。

STROBE要求观察性研究交代研究对象、变量、缺失、样本流程和估计不确定性。论文阶段能否写清这些内容,取决于清洗时有没有保存不同异常的来路;一张只剩最终行数的表,无法替作者回忆谁被删掉以及为什么。

25 行算不回去,比全绿报告更重要

第一次审计模拟库时,程序使用公开 CSV 文件中的能量、蛋白质、膳食纤维、钠和食物类别重新计算评分,发现 25 行与 nutrition_score 不一致。每个总分仍在合法范围内,多数记录也只相差一分,普通的范围检查会把它们全部判为正常。

追查生成过程后,原因落在计算顺序上。旧版生成器先用未四舍五入的内部数值计分,再把营养值保留一位小数写入公开文件。例如,内部蛋白质可能是 17.96 g,旧程序按低于 18 g 得 1 分;CSV 中显示的却是 18.0 g,读者按照已经公开的冻结规则会得到 2 分。总分合法,却不能由读者真正拿到的数据重建。

若只把 25 个总分手工改到一致,眼前的核对会通过,生成器下次运行仍会制造同一问题。真正的修复是改变数据生成顺序:先把准备公开的营养值固定到规定精度,再让同一评分函数读取这些值,随后重新生成全库、低分标记和汇总结果。修订后 1,200 行评分错配为 0,旧错误、代码变化和重新生成时间仍保留在版本记录中。

这个错误还可能越过主要阈值。总分从 5 变成 6 时,imbalanced_lt6 会从“是”变成“否”,低分数量和比例随之变化。“分数处于 0—10”只证明范围合法,没有证明来源正确。清洗最有价值的成果,往往不是删掉多少行,而是发现一条计算链为什么无法重放,并把错误修在最初产生的位置。

NIH 关于研究严谨性与可重复性的说明把方法、数据处理、分析和解释视为连续过程。25 行错配正好说明,处理顺序并不是与科学结论无关的后台细节;它可以改变谁被算作低分,最终进入主要判断。

描述统计是在说明样本长什么样

修复结构和计算来源以后,描述分析才有稳定对象。营养值并不保证左右对称,少数高值会拉动平均数,所以先用中位数表示样本的中间位置,再用第 25 到第 75 百分位之间的四分位范围说明中间一半记录散在哪里。

模拟样本的能量中位数为 532.8 kcal,四分位范围 463.9—604.8;碳水化合物为 70.2 g,范围 58.7—81.1;蛋白质为 15.9 g,范围 11.4—20.3;脂肪为 16.6 g,范围 11.2—22.6;膳食纤维为 3.5 g,范围 2.2—5.0;钠为 725.6 mg,范围 513.1—951.4;食物类别数的中位数是 4 类,范围 3—5;价格为 10.2 元,范围 8.6—11.9。

以蛋白质为例,一半记录落在 11.4—20.3 g,中间位置是 15.9 g,不表示每份早餐都接近 15.9。最小值 3.0 g、最大值 36.7 g 仍然属于分布。直方图可以查看分布是否偏向一侧或混合了几种模式,箱线图可以标出远离中间区域的记录;它们负责提出需要回查的问题,不替研究者决定删除。

四分位范围也不是置信区间。前者描述样本中间一半记录实际分布在哪里,后者描述根据当前样本估计某个总体参数时有多不确定。两个结果都可能写成“下限—上限”,但一个在描述记录,一个在描述估计。WHO 的基础统计与研究方法资料把测量、描述问题大小和比较放在连续路线中,也说明统计量应当跟随变量与问题,而不是每张表机械输出同一套均值和标准差。

55.8% 必须带着它的抽样结构出现

按照冻结的主要定义,总分低于 6 的早餐有 669 份,669 ÷ 1,200 = 55.8%。总分均值为 5.20,中位数为 5,0—10 分都有记录,其中 5 分最多,有 204 份;4 分有 196 份,6 分有 179 份。这些分布让主要比例不再只是一个孤立数字。

若暂时假设 1,200 份早餐彼此独立,常见比例区间算法给出的 95% 估计范围是 52.9%—58.5%。但每家店贡献五份早餐,同店共享菜单和制作方式。按店铺把记录视为一组后,区间变成 52.6%—58.9%,稍微更宽。简单区间没有算错,它只是使用了与实际抽样不符的独立性假设。

数据估计的店内相关系数为 0.067,高于样本量规划时使用的教学假设 0.02。计划参数并没有被现实承诺,它只是数据出现前用于规划的暂定值。模拟数据呈现更强的同店相似,意味着独立信息比原计划略少,区间就应诚实变宽,不能为了守住原先希望的精度继续选更窄的结果。

在六城等权的教学样本、低于 6 分的主要定义和店铺聚类调整下,区间下限仍高于 50%,所以“低分早餐超过半数”这项主要假设得到支持。每一个限定词都必须留在结论里;它不是“中国多数早餐营养失衡”的现实证据。

分组数据还显示,六城低分比例从海川的 47.0% 到北宁的 63.5%,四类店铺从食堂窗口 44.3%、连锁门店 45.2%,到独立门店 59.0%、早餐摊点 68.7%。这些只是直接分组描述,尚未同时考虑城市、店铺构成和食物组成。它们为下一步提出问题,却没有资格在本篇宣布某类店铺造成低分,也不应逐组寻找“显著”结果再挑最醒目的一个。

阈值敏感性不是三个任选的故事

主要界线低于 6 分时,低分比例是 55.8%;改成低于 5 分,比例降到 38.8%;改成低于 7 分,又升到 70.7%。三组数字来自完全相同的早餐和总分,差别只由人为分界造成。

这种更换合理规则、观察结论变化的做法叫敏感性分析。主结果仍按事先冻结的 6 分线回答,另外两个比例用于告诉读者判断有多依赖阈值。研究者不能看完以后挑传播力最强的 70.7%,再把它写成原先计划;也不能因为 38.8% 不支持“超过半数”,就宣布三种算法互相矛盾。

描述分析结束后,数据没有变成一张“没有问题”的完美表。它明确留下了 90 份未实测、148 份配方估算、实际店内相关高于计划,以及主要比例对阈值敏感。这些不是清洗失败的痕迹,而是读者理解结果所需的条件。

下一步模型可以研究城市、店铺和食物组成如何与评分共同变化,却必须继续携带这些条件。若建模以后,质量标记、成组结构和阈值敏感性从报告里消失,模型做的不是进一步解释数据,而是重新把已经看见的问题藏了起来。