2. 临床数据读取与初步探查

临床数据分析的第一步不是建模,而是把数据“安全地搬进来”并看清它的样子。真实项目里,数据可能来自医院信息科导出的 CSV、统计师整理的 Excel、SAS 的 .sas7bdat、SPSS 的 .sav,甚至还有从 EDC 系统导出的固定宽度文本。不同格式的读取方式、编码、缺失值标记、变量类型都不同,如果这一步处理不当,后续所有统计和建模都会建立在错误的基础上。本节围绕“读进来、看结构、查缺失、做描述”四个动作展开,重点讲清每种格式的读取要点和初步探查的判断依据。

一、按格式选择读取函数

pandas 为常见临床数据格式提供了对应的读取函数。CSV 用 pd.read_csv(),Excel 用 pd.read_excel(),SAS 用 pd.read_sas(),SPSS 用 pd.read_spss()。其中 SAS 和 SPSS 的读取依赖 pyreadstat 库,安装命令为 pip install pyreadstat。需要特别注意的是,read_sas 对 .sas7bdat 支持较好,但对 .xpt 传输格式的变量类型和标签处理略有差异;read_spss 能读取变量标签和值标签,这对临床数据中“1=男、2=女”这类编码变量非常关键。

CSV 读取最容易踩的坑是编码和分隔符。国内医院导出的 CSV 常见 gbk 或 gb18030 编码,直接读取会报 UnicodeDecodeError。稳妥做法是先尝试 encoding='utf-8',失败后改用 encoding='gb18030'。分隔符方面,有些系统导出的是制表符分隔,需要指定 sep='\t'。Excel 读取则要注意工作表名和表头行位置,临床表格常在前几行放标题和说明,真正表头可能在第三行,此时用 header=2 指定。

import pandas as pd

# CSV:先试 utf-8,失败换 gb18030
df = pd.read_csv("lab_results.csv", encoding="gb18030")

# Excel:指定工作表和表头行
df = pd.read_excel("patient_demo.xlsx", sheet_name="Sheet1", header=2)

# SAS:读取 sas7bdat
df = pd.read_sas("adsl.sas7bdat", format="sas7bdat", encoding="utf-8")

# SPSS:读取 sav,保留值标签
df = pd.read_spss("questionnaire.sav", convert_categoricals=True)

读取后不要急着分析,先做一次“结构体检”。df.shape 看行列数,df.head() 看前几行,df.dtypes 看每列类型,df.info() 一次性给出非空计数和内存占用。临床数据中常见的类型问题包括:年龄被读成字符串、日期被读成 object、数值列因含“<”“>”符号变成 object。这些都要在探查阶段发现并处理。

二、缺失值概览:先看分布,再决定策略

缺失值在临床数据中几乎不可避免,关键不是“有没有”,而是“缺在哪、缺多少、是否与分组相关”。用 df.isnull().sum() 可以得到每列缺失数量,配合 df.isnull().mean() 得到缺失比例。更直观的方式是按缺失比例排序后输出,快速定位问题列。

missing = df.isnull().sum()
missing_rate = (missing / len(df)).sort_values(ascending=False)
print(missing_rate[missing_rate > 0])

判断缺失是否随机,可以做一个简单的分组对比:按治疗组或性别分组,看关键变量的缺失比例是否接近。如果 A 组缺失 5%、B 组缺失 30%,说明缺失可能与分组相关,直接删除会引入偏倚。此时应优先考虑多重插补或敏感性分析,而不是简单 dropna()。对于缺失比例超过 40% 的变量,要结合临床意义判断是否保留;对于关键结局变量缺失,必须回溯原始数据核实,不能默认填补。

三、描述性统计:用数字和分布建立直觉

df.describe() 给出连续变量的计数、均值、标准差、最小值、四分位数和最大值。临床数据中要特别关注最小值、最大值是否超出合理范围,例如年龄出现 200、收缩压出现 0,这些往往是录入错误或缺失值被编码成 999 的结果。分类变量用 df['sex'].value_counts(dropna=False) 查看频数和缺失,dropna=False 能把缺失单独列出来。

对于偏态分布的指标,如住院天数、费用、CRP,均值和标准差会被极端值拉偏,此时应同时看中位数和四分位距。可以按分组做描述统计,例如 df.groupby('treatment')['age'].describe(),快速比较两组基线是否均衡。这一步也是后续做基线表(Table 1)的基础。

读取 SAS/SPSS 时,变量标签和值标签是重要信息。用 pyreadstat.read_sas7bdat() 或 read_spss() 的 meta 输出可以拿到标签,建议在探查阶段就把标签整理成字典,避免后续反复查编码手册。

四、把探查流程串起来

下面这张图把“多格式读取 → 结构检查 → 缺失概览 → 描述统计 → 判断与处理”串成一条可复用的探查链路,图中标注了每一步的关键函数和典型判断点,便于在实际项目中按图执行。

临床数据读取与初步探查链路 多格式读取 read_csv / read_excel read_sas / read_spss 编码 · 分隔符 · 表头行 结构检查 shape / head / dtypes info() 类型异常 · 日期格式 缺失值概览 isnull().sum() isnull().mean() 缺失比例 · 分组对比 描述统计 describe() value_counts() 分组基线比较 判断点:缺失比例 · 类型异常 · 极值范围 · 分组均衡性 缺失 <5% 可考虑删除或简单填补;缺失 >40% 需评估变量保留价值;关键结局缺失必须回溯原始数据 删除 / 填补 dropna · fillna 中位数 / 众数 / 前向填充 适用于缺失比例低且随机 多重插补 IterativeImputer MICE · 链式方程 适用于缺失非随机或比例较高 敏感性分析 比较不同处理结果 完整案例 vs 插补后 验证结论稳健性 输出:清洗后的分析数据集 + 缺失处理记录 + 基线描述表

把这条链路走完,才算真正“认识”了这份数据。实际项目中,建议把探查结果写成一份简短的记录:每列的类型、缺失比例、异常值范围、处理方式。这份记录既是数据质量的凭证,也是后续统计分析和建模的可追溯依据。读取和探查看似基础,但它决定了整个分析的可信度,值得花时间做扎实。