1. 从上一篇文章的断点说起:EpiData 数据库的第二层功夫
做现场流行病学调查的人,几乎都绕不开 EpiData 这个老牌工具。第一篇文章里我聊的大多是“怎么把一张纸质问卷变成一堆 .rec 文件”——建变量、定字段类型、设取值范围,属于把地基打平。但真正干过项目的人都知道,一个能用的数据库和一堆能跑的 .rec 文件,中间差了整整一层功夫:逻辑校验、变量间约束、跳转关系、双录入比对、数据导出规范。这些才是决定你后期清理数据要熬几个通宵的关键。
这篇就接着上一回往下说,主题是EpiData 数据库建立方法(二)——从“能录入”进化到“录得对、错得少、查得快”。核心关键词围绕CHK 校验文件、逻辑关系设置、双录入比对、数据导出与清洗准备这几块展开。不管你用的是 EpiData Entry 3.x 还是 4.x 系列,思路是通用的,只是菜单位置略有差异,我下面会以 3.1 为主、顺带提一下 4.x 的差异点。
先说清楚这篇文章适合谁看。如果你刚学会建 .rec 文件,能手动录数据但一录错就得回头翻纸质问“这 2 到底是不是 12”,那这篇就是给你的;如果你已经是项目里的数据管理员,负责给十几号调查员发数据库,那这篇更适合当 checklist 用——我踩过的坑基本都写在里面了。EpiData 最大的价值不在于存数据,而在于录入那一刻就把错误挡在门外,这一点想明白了,后面的活能省一大半。
提示:本篇所有操作都基于一个虚构场景——某高校一个关于慢性病危险因素的横断面调查,问卷约 60 个变量,包含跳转题、多选题、日期题和数值题。你遇到的真实问卷千差万别,但对应的处理手法可以对号入座。
2. CHK 校验文件:让数据库自己“挑错”的核心机制
2.1 为什么一定要单独建 CHK 文件,而不是在 REC 里写死
刚上手的人常问:.rec 里不已经能设变量类型和范围了吗,为什么还要多此一举搞个 .chk?这个问题的答案决定了你后面维护数据库的痛苦程度。.rec 定义的是“数据长什么样”,.chk 定义的是“数据之间讲不讲道理”。前者是单变量约束——年龄在 0 到 120 之间、性别只能填 1 或 2;后者是多变量联动——如果性别填 1(男),那么“是否怀孕”这一题必须留空;如果跳转题“近一年是否体检”选了“否”,那后面一串体检项目都必须跳过。
分开建还有一个非常现实的理由:CHK 文件是相对独立的文本文件,出问题好改、好对比、好交接。我在项目里遇到过这样的情况:同一份问卷,三个中心各自微调过录入规则,最后合并数据时发现校验逻辑不一致,靠的就是把三份 .chk 拉出来逐行 diff 才定位到问题。如果这些逻辑全塞在 .rec 里,改一处就可能牵动字段结构,风险高得多。
从操作层面讲,EpiData 里面向 .rec 文件生成 .chk 的入口通常在菜单栏的“文件 → 新建 → 校验文件”或者直接在打开 .rec 后点“生成 CHK 文件”。生成的骨架长这样:
LABEL BLOCK DEFINE 年龄 DEFINE 性别 END 年龄 RANGE 0 120 TYPE COMMENT 请输入实际年龄,不满一岁填 0 END END 性别 RANGE 1 2 MUSTENTER END这里最关键的概念是BLOCK。EpiData 的 CHK 以“块”为单位组织校验逻辑,一个块对应一个变量或一组变量。块与块之间是顺序执行还是条件执行,靠IF ... THEN、GOTO、HIDEBLOCK之类的控制语句来调度。很多人第一次写 CHK 就是在这里犯晕:把一堆RANGE全堆在一个块里,结果校验根本不生效,因为实际录入时变量根本不在同一个块上下文中。
2.2 单变量约束的粒度选择:宁松勿紧,还是宁紧勿松
这个问题没有标准答案,但有个判断口径我用了很多年——看这个变量录错之后能不能被外部信息纠回来。举个例子,年龄这种变量,理论上 0 到 120 都合法,但在一项成年人慢性病调查里,你会看到 14 岁的记录吗?如果调查对象明确是 18 岁以上,那么RANGE 18 110是合理的,而且能立刻抓住录入员把“48”打成“4”这种情况。
反过来,血压这种变量,如果你设RANGE 60 250,看起来合理,但实际上会有调查对象收缩压 58 这种真实存在的情况(尤其老年人),设太窄就会把真数据挡在外面,逼着调查员去改数。我的一般原则是:逻辑上绝不可能出现的值,设硬范围;生理上少见但可能存在的值,只做警告提示(用TYPE WARNING而不是直接拒绝)。
| 约束类型 | 适用变量 | 设置方式 | 风险 |
|---|---|---|---|
| 硬范围拒绝 | 性别、选项编码、日期年份 | RANGE 1 2、TYPE默认 | 设错会挡真数据 |
| 软警告 | 年龄、身高体重、血压 | RANGE+TYPE WARNING | 提示被忽略则无效 |
| 必填 | 关键标识变量(ID、日期) | MUSTENTER | 无 |
| 自定义表达式 | 派生变量、一致性检查 | IF ... THEN ... | 逻辑写错难排查 |
这里补一个实操细节:EpiData 的TYPE WARNING只是弹提示,录入员按回车仍可继续录入。如果你想让提示真正起作用,得在项目培训时明确讲清楚“看到黄色提示必须回查原始问卷”,否则它就是一个被习惯性忽略的弹窗。我在一个项目里统计过,未经过提醒的警告被忽略率超过六成,讲清楚之后降到一成以下。工具是死的,流程得是活的。
2.3 变量命名与块组织:为三个月后的自己着想
CHK 文件写多了会有一种错觉——反正就是几十行,随手写写得了。三个月后你回来改,看到IF a1 = 2 THEN b2 = .这种代码,保证想不起来 a1 是啥。所以我在项目里强制两条规矩:
- 变量名用有意义的英文或拼音缩写,绝不使用 v1、v2、q1 这类纯序号。比如
age_y、sex、has_exam、exam_date。拼音也可,但同一项目内必须统一,别一半拼音一半英文。 - 每个块前加
LABEL说明,把题目原话抄一部分进去。CHK 文件支持注释,*开头的是注释行,别省这点字数。
* 变量名: has_exam 题目: 近一年是否参加过健康体检 * 来源: 问卷第 12 题 DEFINE has_exam END has_exam RANGE 1 2 MUSTENTER TYPE COMMENT 1=是 2=否 END END这一段看着啰嗦,但当你手上同时有五个项目的 CHK 在改时,这个习惯能救命。我见过一个真实案例:某项目交接时原负责人已离职,CHK 里全是无注释的q12、q13,新接手的人花了整整两周对着纸质问卷逐条反推逻辑,效率损失非常大。
3. 逻辑关系与跳转控制:复杂问卷的真正战场
3.1 跳转题的三种实现路径与取舍
问卷里的跳转题是最容易录错的地方。比如“近一年是否参加过体检”,选“否”的人,后面日期、机构、项目数三题都应该跳过。EpiData 里实现跳转大致有三条路:
第一条:靠GOTO硬跳。在 has_exam 块里写IF has_exam = 2 THEN GOTO skip_exam,然后在后续变量块前面放一个LABEL skip_exam占位。这是最直观的做法,缺点是问卷一改跳转点,所有 GOTO 标签都得跟着挪,维护成本高。
第二条:靠HIDEBLOCK/SHOWBLOCK动态隐藏。根据前面的值决定后续块显示还是隐藏。这种方式看起来优雅,但 EpiData 3.x 对块的显示控制支持有限,实测中容易出现“隐藏了但焦点还在里面”的尴尬情况,需要配合JUMP才有好效果。4.x 版本在这块好一些。
第三条:不跳转,但用IF ... THEN强制置空。也就是选“否”时,直接在 CHK 里把后续变量置为缺失值,同时在这些变量自己的块里加条件校验:IF has_exam = 2 THEN ... 必须为空。这条路最笨但最稳,尤其适合多人协作、问卷版本多的项目。
我个人的选择是第三条为主、第一条为辅。原因很实际:跳转做复杂了,录入员一旦按错键退回去改前一题,跳转状态就可能错乱,出现“明明选了否,后面还是被要求填”的情况。强制置空 + 条件校验虽然录入时要多按几次回车,但行为确定、可预测,不会出玄学问题。
3.2 多变量一致性校验:把“矛盾数据”挡在录入阶段
真正体现 CHK 价值的是跨变量校验。我列几个横断面调查里高频的规则,你可以直接改成自己的变量名用:
| 校验场景 | 逻辑表达(示意) | 说明 |
|---|---|---|
| 性别与孕产史 | IF sex = 1 AND preg = 1 THEN ERROR | 男性不应有怀孕记录 |
| 年龄与出生年份 | 由出生年推算年龄,差异 > 2 岁则警告 | 抓录入错位 |
| 体检日期与调查日期 | exam_date > survey_date则报错 | 未来日期是典型错误 |
| 吸烟量与吸烟状态 | 状态为“从不吸”但日吸烟量 > 0 则报错 | 跳转漏填 |
| 多选题互斥项 | “无”与其他选项同时被选则报错 | 逻辑互斥 |
| 收入与支出 | 极值比较,超阈值给警告 | 抓小数点错位 |
具体到语法,EpiData 的 CHK 支持在块内写条件判断,也可以写一个独立的“虚拟块”专门做跨变量检查。后一种做法我强烈推荐,因为它的执行时机可控——通常放在一组相关变量之后,作为一个“检查点”。
* 跨变量一致性检查点:性别与孕产史 IF sex = 1 AND preg = 1 THEN TYPE ERROR 男性不应有怀孕记录,请核对第 5 题与第 21 题 END ENDIF注意:EpiData 3.x 的 CHK 语法里,
IF语句的位置有讲究,写在块外通常作为全局检查,写在块内则跟随该块的执行时机。写错位置会导致校验根本不触发,这是新手最高频的“写了等于没写”问题。建议每写完一段就进 Data Entry 手动录几条测试数据验证。
3.3 派生变量的处理:自动算还是录完再算
EpiData 支持在 CHK 里对变量做赋值运算,也就是派生变量,比如由出生日期自动算年龄,或者把身高体重直接换算 BMI。这块我的建议是谨慎使用,能用导出后计算就别在录入端算。
理由有三条。第一,录入端的计算一旦写错,错误会被静默写入数据集,而且很难事后发现——你没法从数据里看出这个 BMI 是算的还是录的。第二,派生逻辑改动后,历史数据不会自动重算,会出现新旧数据口径不一致。第三,CHK 里的表达式语法相对简陋,复杂计算写起来费劲且不易调试。
如果确实要在录入端派生(比如需要现场即时反馈给调查对象),那至少做到两点:派生结果存到独立变量、命名带_calc后缀,并且保留原始输入变量,方便日后重算与核查。
3.4 日期与时间变量的校验陷阱
日期是 EpiData 里最容易出问题的类型。常见坑包括:分隔符不统一(有人打2024-5-1,有人打2024/05/01)、年份只打两位导致世纪误判、农历公历混填。CHK 里可以做的约束有:
- 用
RANGE限定合理日期区间,比如调查日期限定在项目执行期内。 - 用
TYPE强制格式,EpiData 的日期类型本身会做格式校验,但不同版本对分隔符宽容度不同,最好在培训文档里写死一种格式。 - 对“今天”类变量做上限约束,未来日期一律报警。
实测下来,最省事的组合是日期类型 + 合理区间 + 一条“未来日期即报错”的全局检查。别看简单,光这一条就能挡掉录入阶段相当比例的日期错误,而且不增加录入员任何操作负担。
4. 双录入与数据比对:把错误率压到千分位以下
4.1 双录入的必要性与成本权衡
数据管理圈里有句老话:单录入的错误率大概在千分之几,双录入比对后能压到万分之一以下。但双录入意味着双倍人工,很多小项目会因为成本放弃。我的看法是分情况:
- 样本量小、变量少、问卷简单的项目,单录入 + 关键变量二次抽查就够了。
- 涉及关键结局指标、样本量上千、问卷复杂的项目,双录入是底线,省这一点人工后期清数据的代价更大。
- 无论哪种,录入前的培训与录入中的抽检都必不可少,工具替代不了管理。
EpiData 自带双录入比对功能,流程大概是:建一个“双录入模式”的数据库,两个录入员分别录同一批问卷,录完后用“数据录入 → 双录入比对”或者工具菜单里的比对功能,指定两份数据文件,生成差异报告。
4.2 双录入的实际操作流程与注意事项
具体步骤我按实际操作顺序捋一遍:
- 建立双录入目录结构。比如
\project\entry_A\和\project\entry_B\,各自放一份 .rec。别偷懒共用一份,否则后期比对文件和原始数据混在一起,理都理不清。 - 统一双录入的文件编号规则。问卷 ID 必须手工录入且唯一,这是比对的锚点。ID 录错会导致整份问卷对不上号,比对报告里全是“缺失记录”。
- 录入前对齐 CHK 文件。两份数据库必须用同一个 .chk,这一点非常重要。我见过一边用了更新的 CHK、一边用旧的,结果两边校验逻辑不同,比对时差异报告里混进大量“本应被拦下的错误”,白费功夫。
- 控制录入节奏。两个录入员不要同时看到同一份问卷,避免互相影响判断。通常会分成两个批次交错录入。
- 执行比对。EpiData 会生成一份差异清单,通常包含记录编号、变量名、两侧取值。这份清单要打印出来,由第三个人拿着原始问卷逐条核对裁决——注意,裁决不是录入员自己改,必须回到纸质问卷确认哪个是对的,然后由管理员在指定侧修正。
- 修正后重跑比对,直到差异清零或剩余差异均有明确解释(比如已知的问卷字迹不清)。
注意:比对报告里的“差异”不全是错的,有些是两份录入各自合理但取值不同(典型如多选顺序不同)。所以比对前要先约定哪些变量参与比对、哪些属于“允许差异”,这一步不做,差异清单会非常噪杂。
4.3 双录入常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 比对显示大量“记录缺失” | 两侧 ID 录入不一致或顺序错乱 | 先单独核对 ID 列,必要时导出排序后比对 |
| 差异集中在同一变量 | 该变量题面歧义或选项顺序不清 | 回看问卷设计与培训材料,统一口径 |
| 修正后差异反复出现 | 两人对同一题理解不同 | 召开短会明确规则,改培训文档 |
| 比对功能无法启动 | 文件路径含特殊字符或数据被占用 | 关掉所有数据窗口,换简单路径重试 |
| 日期变量差异极多 | 分隔符习惯不同 | 统一格式并在 CHK 中约束 |
这张表是我这几年攒下来的,几乎每个项目都会撞上其中两三条。尤其是第一条和最后一条,出问题的时候人会本能地怀疑软件坏了,其实八成是数据层面的规则没对齐。
4.4 单录入项目的替代方案:抽检与逻辑审核
如果项目实在做不了双录入,至少补上两道防线。一是关键变量 100% 复核,把所有直接影响分析结论的变量(结局、暴露、年龄性别)单独导出,由第二人对着问卷核一遍。二是逻辑审核清单,把 CHK 里没法覆盖的复杂规则(通常涉及三四个变量联动)列成表,导出数据后用统计软件跑一遍,把违规记录挑出来回查。这两道防线加起来,能把错误率压到可以接受的水平,虽然不如双录入彻底,但至少不会在分析阶段才发现数据烂尾。
5. 数据导出与交接规范:数据库的“最后一公里”
5.1 导出格式的选择与编码问题
EpiData 的录入文件不能直接进统计分析软件,中间需要导出。常见路径是.rec → 导出为文本/Excel/SPSS 格式。这里有几个实操要点:
- 导出文本(.txt / .csv)最稳,兼容性最好,但需要注意分隔符和编码。中文变量标签在导出时容易乱码,建议统一用 UTF-8,并在导出前确认标签字段没有超长或含特殊字符。
- 导出 Excel 便于交接和人工核对,但数值型变量的前导零、日期格式会被 Excel 自作主张改掉,导出后必须抽几行核对。
- 直接导出 SPSS 格式能保留变量标签和值标签,省去后期重复定义,但不同版本 SPSS 的兼容性偶有问题,导出后要拿统计软件打开验证。
我个人的习惯是主数据用 CSV、交接给合作方用 SPSS 格式、人工核对用 Excel,三份同时生成,互相印证。别嫌麻烦,一次导出多花十分钟,能省下后期对不上的两天。
5.2 数据字典与变量清单的同步维护
数据库建完,一定要同步产出两样东西:变量清单(变量名、类型、取值范围、对应的问卷题号)和值标签表(每个编码的含义)。这两样东西建议直接从 .rec 和 .chk 里整理,而不是另起一份 Excel 手打,避免不一致。
维护时记住一条原则:任何字段变更必须同时更新三处——.rec、.chk、变量清单。我在项目里推行过“变更登记表”,每改一次字段就登记一行:谁改的、何时改的、为什么改。听起来像官僚流程,但数据交接时这份登记表的价值极高,尤其当项目周期跨年、人员流动频繁的时候。
5.3 交接前的自检清单
数据交付给分析团队之前,我会跑一遍固定清单,摊在这里供你参考:
- 抽查 5 到 10 份问卷,逐个变量与原始纸质记录比对,确认录入与导出无损。
- 确认所有逻辑校验在测试数据上都能正常触发,没有“写了但无效”的块。
- 确认导出文件的记录数与问卷回收数一致,无多无少。
- 确认缺失值编码统一(EpiData 的缺失与统计软件的空值语义要对上,别把“9=不详”和“空白”混成一类)。
- 确认变量名、标签、值标签与变量清单完全一致。
- 备份原始 .rec/.chk 文件与导出数据,至少两份介质,命名带日期。
这套流程跑下来,一个中等规模项目大概多花半天到一天,但能极大降低“数据交出去后被退回来返工”的概率。返工的代价,做过的人都懂。
提示:备份这件事我单独强调一次。EpiData 文件体积小,备份成本几乎为零,但丢了就是灾难。我见过不止一个项目把数据库放在单台电脑上,硬盘故障后只能用导出的半成品数据重建,前功尽弃。
6. 我个人在实际操作中的几点体会
写到这里,技术上该说的基本说完了,最后分享几条不写在手册里、但真正影响项目成败的经验。
第一,CHK 写得漂亮不如写得稳定。我早期喜欢把校验逻辑写得非常密,恨不得每个变量都加五条约束,结果录入员怨声载道,遇到弹窗直接无脑按回车,校验形同虚设。后来我改成“关键路径紧、边缘变量松”,弹窗数量降下来,录入员的注意力反而集中了,效果更好。工具设计要考虑人的行为,这一点比语法本身重要得多。
第二,培训录入门槛比优化数据库更划算。同样一套数据库,培训半小时和培训两小时,出来的数据质量差距肉眼可见。培训里重点讲三件事就够:ID 怎么编、遇到弹窗怎么办、哪些题容易看错。其他都是次要的。
第三,留出“已知问题清单”。每个项目做到后期都会有那么几条无法彻底解决的数据问题,比如某几份问卷字迹不清、某个变量口径在项目中期调整过。把这些明确列出来交给分析团队,比藏着掖着强。分析阶段的人最怕的不是数据有瑕疵,而是不知道瑕疵在哪。
第四,别迷信工具的自动化。EpiData 的校验再全,也挡不住问卷设计本身的歧义;双录入比对再细,也挡不住两个录入员共同的误读。数据库只是流程的一环,问卷设计、培训、抽检、复核,哪一环松了都会在数据里体现出来。建库这件事,功夫在库外。
如果后面还要继续往下扩展,我大概会聊聊怎么把 EpiData 导出的数据接入统计分析流程、缺失值的处理策略,以及多中心项目里数据库版本的管理办法。这些内容都能单独成篇,等遇到合适的项目再整理。