2. Python临床数据读取与整合

临床分析报告自动化的第一步,不是画图,也不是建模,而是把散落在不同系统、不同格式中的原始数据,稳定、可追溯地读入 Python,并整合成一张结构清晰、语义明确的分析用表。真实临床环境里,数据来源往往包括:电子病历系统导出的 CSV、检验科提供的 Excel 报表、统计软件遗留的 SAS 数据集、SPSS 保存的 .sav 文件,甚至还有手工录入的随访表。这些数据在字段命名、编码方式、缺失表示、时间格式上几乎不可能天然一致。因此,本节的核心任务是:用 pandas 建立一条可复用的读取与整合链路,把“多源、脏乱、分散”的临床数据,变成“单表、规范、可分析”的数据集。

一、读取不同格式的临床数据源

pandas 对不同格式提供了对应的读取函数。CSV 用 pd.read_csv(),Excel 用 pd.read_excel(),SAS 用 pd.read_sas(),SPSS 用 pd.read_spss()。读取时最关键的不是“能读进来”,而是“读得对”。临床数据常见的坑包括:编码不是 UTF-8 导致中文乱码、缺失值被写成 . 或 -9、日期被识别成字符串、数值列混入单位符号。下面是一段典型的读取示例:

import pandas as pd

# 读取检验科 Excel 报表
lab = pd.read_excel(
    "lab_results.xlsx",
    sheet_name="生化",
    na_values=["", "NA", "null", "-9", "999"],
    dtype={"patient_id": str, "item_code": str}
)

# 读取随访 CSV,指定编码与日期解析
followup = pd.read_csv(
    "followup.csv",
    encoding="gbk",
    parse_dates=["visit_date"],
    na_values=["", "未知", "无"]
)

# 读取 SAS 数据集
sas_data = pd.read_sas("demographics.sas7bdat", format="sas7bdat")

# 读取 SPSS 文件
spss_data = pd.read_spss("questionnaire.sav")

这里有两个关键动作:一是用 na_values 显式声明哪些值应被视为缺失,避免 -9 被当成真实数值参与统计;二是用 dtype 把患者 ID、项目编码强制为字符串,防止前导零丢失或 ID 被误当数字。日期列用 parse_dates 直接解析,比读入后再转换更可靠。

二、缺失值与异常值的处理策略

临床数据中的缺失不是随机噪声,它往往携带信息。比如“未测”可能意味着医生判断不需要测,“拒绝回答”可能与社会因素相关。因此处理缺失值前,先要区分缺失类型:完全随机缺失、随机缺失、非随机缺失。pandas 中可用 df.isna().sum() 快速统计每列缺失数量,用 df.isna().mean() 看缺失比例。

处理策略上,数值型变量常用中位数填充,类别型变量常用众数填充或单独设为“未知”类别。但更稳妥的做法是:对缺失比例低于 5% 的列可考虑填充;对缺失比例高于 30% 的列,应谨慎使用,必要时在报告中说明。异常值方面,临床指标常有生理范围,例如成人收缩压低于 60 mmHg 或高于 250 mmHg 通常提示录入错误。可用 IQR 法或业务规则标记:

# 标记异常血压
bp = merged["systolic_bp"]
q1, q3 = bp.quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
merged["bp_outlier"] = (bp < lower) | (bp > upper)

注意:异常值不等于错误值。标记后应结合原始记录判断,而不是直接删除。

三、编码映射:让字段语义统一

不同数据源的编码体系往往不同。比如性别在 A 表是 1/2,在 B 表是 M/F,在 C 表是 男/女。整合前必须建立统一映射。pandas 中可用 map() 或 replace() 完成:

sex_map = {"1": "男", "2": "女", "M": "男", "F": "女", "男": "男", "女": "女"}
merged["sex"] = merged["sex_raw"].astype(str).str.strip().map(sex_map)

对于检验项目编码,建议建立一张“项目字典表”,包含 item_code、item_name、unit、ref_low、ref_high,再用 merge() 关联。这样后续报告中的项目名称、单位、参考范围都能自动带出,避免硬编码。

四、多表关联:从分散到统一

临床数据整合的核心是关联。常见关联键包括 patient_id、visit_id、sample_id。关联前要检查键的唯一性和类型一致性。例如人口学表一个患者一行,检验表一个患者多次就诊多行,此时应按 patient_id + visit_date 关联,或先聚合再关联。

# 人口学与检验结果按 patient_id 左连接
merged = pd.merge(
    demo,
    lab,
    on="patient_id",
    how="left",
    validate="one_to_many"
)

# 随访表按 patient_id 和 visit_date 关联
merged = pd.merge(
    merged,
    followup,
    on=["patient_id", "visit_date"],
    how="left",
    suffixes=("", "_fu")
)

validate 参数非常重要,它能在关联时检查“一对多”“多对一”是否符合预期,防止因重复键导致行数膨胀。关联后应检查行数变化:如果左表 1000 行,关联后变成 3000 行,说明右表存在重复键,需要先去重或聚合。

临床多源数据读取与整合链路 数据源层 CSV 随访表 read_csv / gbk Excel 检验报表 read_excel / sheet SAS 数据集 read_sas / sas7bdat SPSS 问卷 read_spss / sav 项目字典表 item_code / unit 清洗与规范化层 缺失值识别 na_values / isna 异常值标记 IQR / 业务规则 编码映射 map / replace 类型与日期统一 dtype / parse_dates 多表关联层 人口学表 patient_id 唯一 检验结果表 patient_id + visit_date 随访表 visit_date 分析用宽表:一行一次就诊,字段语义统一

五、整合后的质量检查

整合完成不等于可以分析。建议至少做四项检查:第一,行数与预期是否一致,是否因关联产生重复;第二,关键字段缺失率是否在可接受范围;第三,数值型字段的分布是否合理,如年龄是否出现负数或超过 120;第四,分类字段的取值是否都在映射表内,有没有出现“未映射”的 NaN。可以用一段简短的检查代码:

print("行数:", len(merged))
print("缺失率:\n", merged.isna().mean().sort_values(ascending=False).head(10))
print("年龄范围:", merged["age"].min(), merged["age"].max())
print("性别取值:", merged["sex"].value_counts(dropna=False))
建议把读取、清洗、映射、关联的每一步都保存为独立函数,并用 df.shape 和 df.head() 在关键节点打印中间结果。这样一旦最终数据异常,可以快速定位是哪一步引入的问题。
不要用 dropna() 直接删除所有含缺失的行。临床数据中,一个患者可能只有某项指标缺失,直接删除会损失大量有效样本,也可能引入选择偏倚。应先评估缺失模式,再决定填充、标记还是删除。

完成本节的数据读取与整合后,你将得到一张以患者或就诊为单位、字段命名统一、缺失与异常已标记、编码已映射的分析用宽表。这张表是后续统计描述、分组比较、图表生成和报告自动化的共同基础。数据整合的质量,直接决定最终报告的可信度,因此宁可在此阶段多花时间检查,也不要带着隐患进入分析环节。