4. 临床数据整合与合并
在真实的临床数据工程中,数据几乎从不以“一张干净的大表”形式存在。检验系统(LIS)里是每次检验的明细行,医嘱系统(CPOE)里是每次开单记录,电子病历(EMR)里是病程与诊断,随访系统里是定期访视结果。要把它们变成可分析的队列数据集,核心工作就是三件事:横向的多表连接、纵向的数据拼接,以及贯穿始终的患者主索引(EMPI)构建。本节围绕这三件事展开,并重点处理不同来源数据的时间对齐与冲突消解。
一、先明确“连接键”与“时间语义”
多表连接的第一步不是写 merge,而是确定连接键。临床数据中常见的键有三类:患者标识(住院号、门诊号、病案号、身份证号)、就诊标识(visit_id / encounter_id)、事件标识(检验单号、医嘱号)。连接键选错,结果会静默膨胀或丢失。
时间语义同样关键。同一张表里往往并存多种时间:order_time(开单时间)、specimen_time(采样时间)、report_time(报告时间)、charge_time(计费时间)。做“入院后首次肌酐”这类分析时,应以采样时间为准,而不是报告时间,否则会把入院前采样、入院后才出报告的结果错误地算进来。因此建议在整合前统一约定:临床事件时间 = 采样/执行时间,并保留原始时间列以便追溯。
二、多表连接:从明细到宽表
以“患者基本信息 + 检验结果 + 诊断”为例,典型做法是:先以患者主索引为基准表,左连接检验明细,再左连接诊断。左连接保证不丢失没有检验或没有诊断的患者。需要警惕的是一对多连接导致的行膨胀:一个患者有多条诊断,若直接与检验明细连接,检验行会被复制多份。正确做法是先对诊断按患者聚合(如取主要诊断),再连接。
import pandas as pd
# 诊断表按患者聚合,避免一对多膨胀
dx_main = (dx.sort_values("dx_priority")
.groupby("patient_id", as_index=False)
.first()[["patient_id", "main_dx", "dx_code"]])
# 以患者主索引为基准左连接
cohort = (empi.merge(dx_main, on="patient_id", how="left")
.merge(lab, on="patient_id", how="left"))
print(cohort.shape, cohort["patient_id"].nunique())
连接后必须做行数与唯一患者数校验:若 cohort.shape[0] 远大于检验明细行数,说明发生了膨胀,需回到聚合步骤排查。
三、纵向拼接:同一患者多次访视的堆叠
纵向数据拼接(concat / append)解决的是“同一结构、不同时间点”的合并,例如基线、第1周期、第2周期的随访表。拼接要求列名与含义一致,并新增一列标识来源时点:
visits = []
for tp, df in [("baseline", v0), ("cycle1", v1), ("cycle2", v2)]:
df = df.copy()
df["visit"] = tp
visits.append(df)
long_df = pd.concat(visits, ignore_index=True)
long_df = long_df.sort_values(["patient_id", "visit_date"])
拼接后按 patient_id + visit_date 排序,才能正确计算相邻访视的指标变化。若同一患者同一时点出现重复记录,需要按预设规则去重,例如保留最新录入或标记为冲突待人工核查。
四、患者主索引构建与冲突消解
不同来源系统对同一患者的标识往往不一致:LIS 用住院号,随访系统用手机号,EMR 用病案号。构建 EMPI 的思路是确定性匹配 + 概率匹配结合。确定性匹配用身份证号、住院号等强标识;概率匹配用姓名、性别、出生日期、手机号等组合打分。
冲突处理遵循“强标识优先、时间就近、来源可信度排序”三条原则。例如同一患者两条记录性别不一致,以身份证号解析结果为准;若都无身份证,则以最近一次就诊记录为准,并打上 conflict_flag 供人工复核。
五、时间对齐:把事件放到统一时间轴上
时间对齐的目标是回答“某次检验相对于入院/用药是第几天”。常用做法是计算相对时间:relative_day = (event_time - index_time).days,其中 index_time 为入院时间或首次用药时间。参照系一旦确定,全文与图示必须保持一致:本课程约定入院当天为第 0 天,入院前为负值,入院后为正值。
对于需要“窗口内取值”的场景,例如“用药后 7 天内最低血小板”,可用时间窗筛选后再聚合:
win = lab[(lab["relative_day"] >= 0) & (lab["relative_day"] <= 7)]
min_plt = win.groupby("patient_id")["plt"].min()
下面这张图概括了从多源数据到统一分析队列的整合链路,重点标出连接键、时间对齐点与冲突消解环节。
六、整合后的校验清单
整合完成不等于可用。建议固定执行以下检查:
- 行数与唯一患者数:连接后行数是否等于预期明细行数,唯一患者数是否与 EMPI 一致。
- 时间轴单调性:同一患者
relative_day是否随事件顺序递增,是否存在负值异常。 - 冲突标记数量:
conflict_flag占比过高说明匹配规则过松,需收紧阈值。 - 关键字段缺失率:如主要诊断、关键检验值缺失比例,决定是否需要补充数据源。
至此,多源临床数据被整理为一张以患者为行、以时间轴为序、带冲突标记的分析宽表。后续的特征工程、缺失处理与建模,都建立在这一整合结果之上。