3. 研究方案到数据采集表的映射
临床研究方案通常以自然语言描述“要收集什么”,而Python队列工程需要的是“机器能校验、能落库、能追溯”的结构化定义。两者之间的桥梁,就是数据采集规范(Data Collection Specification)。它不是一张简单的CRF复印件,而是把CRF字段、变量定义、编码规则、多中心差异和版本信息统一收敛为可执行对象的过程。本节的核心任务是:建立从方案到采集规范的映射方法,并让这套规范在Python中真正可运行。
一、映射的三层结构:字段、变量、规则
一个CRF页面上的“性别”输入框,在工程视角下至少对应三层信息。第一层是采集字段:它在表单上的标签、控件类型(单选/文本/日期)、是否必填、所在访视。第二层是变量定义:它在数据库中的变量名(如 sex)、数据类型(枚举/整数/日期)、允许值集合、缺失值编码。第三层是编码规则:原始选项如何转换为分析用编码,例如“男→1、女→2、未知→9”,以及是否允许“未填”与“未知”并存。
很多项目失败的原因,是把这三层混在一张Excel里:字段名用中文、变量名用拼音、编码规则写在批注中。正确做法是让每一层都有明确归属,并用一个稳定的字段ID贯穿始终。字段ID建议采用“表单缩写_序号”的形式,如 DM_001,它不随标签文字变化而变化,是版本控制和多中心合并的锚点。
二、用Python数据结构承载采集规范
采集规范最直接的落地形式,是一个可被程序读取的字典或数据类。下面给出一个最小但完整的示例,覆盖字段、变量、编码和校验四类信息:
from dataclasses import dataclass, field
from typing import Literal
@dataclass
class FieldSpec:
field_id: str # 稳定锚点,如 DM_001
label: str # 表单显示标签
var_name: str # 数据库变量名
dtype: Literal["enum", "int", "float", "date", "str"]
required: bool
allowed: list | None = None # 枚举允许值
code_map: dict | None = None # 原始选项 -> 分析编码
missing_code: int | None = None
sex_spec = FieldSpec(
field_id="DM_001",
label="性别",
var_name="sex",
dtype="enum",
required=True,
allowed=["男", "女", "未知"],
code_map={"男": 1, "女": 2, "未知": 9},
missing_code=9,
)
这段代码的价值在于:字段ID、变量名、编码映射三者被绑定在同一个对象上。后续无论是生成录入界面、写校验函数,还是做多中心合并,都围绕这个对象展开,而不是在各处重复硬编码。
三、多中心表单差异的处理策略
多中心研究中最常见的问题是:A中心用“1=男、2=女”,B中心用“M=男、F=女”,C中心直接填中文。如果每个中心各写一套解析逻辑,维护成本会迅速失控。更稳妥的做法是中心适配层 + 统一规范层:各中心保留自己的原始表单和原始编码,适配层负责把原始值映射到统一规范中的标准编码。
适配层可以用一个“中心编码字典”表达:
center_code_maps = {
"A": {"1": "男", "2": "女", "9": "未知"},
"B": {"M": "男", "F": "女", "U": "未知"},
"C": {"男": "男", "女": "女", "未知": "未知"},
}
def normalize_sex(raw_value, center):
mapped = center_code_maps[center].get(str(raw_value))
if mapped is None:
raise ValueError(f"中心{center}出现未定义性别编码: {raw_value}")
return sex_spec.code_map[mapped]
这样,统一规范层只认“男/女/未知”,各中心的差异被隔离在适配层。新增中心时只需增加一条映射,不需要改动下游分析代码。需要强调的是,适配层必须对未定义编码显式报错,而不是静默转为缺失,否则会掩盖数据质量问题。
四、版本控制:让规范可追溯、可回滚
采集规范会随方案修订而变化:新增字段、修改选项、调整必填性。如果没有版本控制,历史数据与当前规范就会错位。建议为规范引入语义化版本号(如 v1.2.0),并遵循三条规则:新增可选字段升次版本号;修改编码映射或必填性升主版本号;仅修正文字标签升修订号。
每次变更都应记录变更日志,至少包含:版本号、生效日期、变更字段ID、变更前后内容、影响的数据表。在Python中,可以把版本信息写入规范对象的元数据,并在数据入库时一并保存 spec_version,这样任何一条记录都能追溯到它当时遵循的规范版本。
五、映射关系的整体视图
下图展示了从研究方案到可执行采集规范的完整映射链路,以及多中心适配层和版本控制所处的位置。图中左侧是方案与CRF来源,中部是规范的三层结构,右侧是Python可执行对象与落库结果,底部是贯穿始终的版本线。
图中可以看到,版本控制不是附加项,而是贯穿来源、规范、适配和执行的主线。任何一次字段或编码变更,都应同时更新规范对象、适配映射和版本号,三者缺一不可。
六、落地检查要点
- 字段ID唯一且稳定:不随标签文字变化,作为多中心合并和版本对比的锚点。
- 编码映射显式化:所有选项到分析编码的转换必须写在
code_map中,禁止在分析脚本里临时判断。 - 缺失值语义明确:区分“未填”“未知”“不适用”,并分别赋予不同编码,避免一律填0或空字符串。
- 适配层报错而非静默:遇到未定义编码时抛出异常,保留原始值以便回溯。
- 版本随数据落库:每条记录保存其遵循的
spec_version,使历史数据在规范升级后仍可解释。
把研究方案转化为可执行采集规范,本质上是把“人读的规则”翻译成“机器可校验的对象”。字段ID、变量定义、编码映射和版本号四者绑定后,多中心差异被隔离在适配层,下游分析只面对统一、可追溯的数据。这样,后续的清洗、质控和统计才能建立在稳定可靠的基础之上。