拓冰建站拓冰建站
首页 / 资讯中心 / 正文

NHANES数据清洗指南:缺失值编码与多周期合并的坑与解法

简介这是一份面向临床科研人员与R语言使用者的NHANES数据清洗代码包针对美国国家健康与营养调查数据量大、格式复杂、缺失值多等痛点系统提供从数据选择、合并、清洗、缺失插补到协变量筛选的完整可复现流程。包内共19个文件包括9个CSV数据文件、2个R脚本、2个Shell运行脚本以及Markdown说明文档、HTML报告、PNG分布图等辅助内容压缩包大小约149.99MB既便于对照学习也可直接复用。目前已有442人学习适合正在处理NHANES数据、希望获得规范清洗思路的中级数据分析用户。借助该代码包可重点掌握tidyverse与haven的数据合并技巧运用mice包完成多重插补学会剔除无效值、筛选协变量并规范化列名最终生成干净整洁、可直接用于统计建模的数据集大幅提升分析效率。 我第一次跑NHANES数据的时候血压那一列的888和777就差点把我忽悠瘸了。当时我以为是极端高血压病人差点把这个变量当异常值整列清掉直到翻到数据字典才发现这些三位数对应的是“拒绝测量”、“检测失败”之类的特殊缺失标记。这件事之后我养成了一个习惯任何NHANES文件读进来第一件事不是跑描述统计而是先对照Codebook把缺失编码理清楚。这篇指南就是围绕这个习惯展开的。我会以一个真实周期2017-2018也就是带_J后缀的那一批为例讲清楚NHANES数据结构、缺失值编码、多文件合并、权重保留和重复周期拼接这些事并且在最后给出一套可以直接跑的pandas清洗流水线代码。适合刚接触NHANES的医学研究生、公共卫生从业者以及一切被XPT文件和777/999逼疯过的科研人。1. 两年一个周期的文件迷宫SEQN才是唯一钥匙1.1 NHANES的数据结构比想象中更“碎”NHANES不是一张大宽表它是按两年一个周期发布的每个周期下面又有几十个按检查模块拆分的独立数据文件。比如人口学信息在DEMO_*.XPT里血压检查结果在BPX_*.XPT里饮食摄入在DR1TOT_*和DR2TOT_*里实验室检测又拆成血清、全血、尿液等多个文件。这个设计对于CDC来说是合理的因为不同检查项目覆盖的人群不一样——有的是全年龄段有的只针对2岁以上有的只检查20岁以上根本没办法塞进同一张表。但对于我们做清洗的人来说这就意味着很多字段的缺失其实不是“数据没采集”而是“这个人压根不在这个检查子样本里”。这两种缺失在分析时的处理方式完全不同后面会细说。各文件之间靠SEQN这个受访者ID进行横向拼接它在一个周期内是唯一的。整个NHANES清洗的第一原则就是任何分析数据集主键都是SEQN。如果你发现合并之后SEQN有重复或者某个表里SEQN出现多次先别急着去重搞清楚这个文件是不是存在重复测量记录再说。1.2 缺失值不是pandas默认的那套NaNNHANES的缺失编码体系非常复杂而且不同周期、不同变量之间并不完全一致。常见的大致有几类数值型大编码比如777表示“拒绝回答”999表示“不知道”888表示“无法检测或无法确定”小数编码比如.A、.B这类SAS特殊缺失pandas读取时往往会转成NaN字符型的空值和b多见于字符变量真正的数值缺失比如没做某项检查该字段就是空。麻烦的地方在于pandas的read_sas在读取XPT文件时对某些缺失编码会直接转成NaN对另一些却保留成777这种“看似正常的数值”。所以你不能假设读进来就已经清洗好了数据字典才是唯一的裁判。2. 动手清洗前先花十分钟读懂命名与字典2.1 变量名后缀和周期编号的对应关系NHANES文件名里的后缀字母是周期编号不是乱标的。_A对应1999-2000周期_B对应2001-2002依此类推到_J就是2017-2018。这个规律对跨周期合并很重要因为不同周期里同一个变量的名称也可能带上不同后缀比如某些问卷变量在旧周期叫DPQ010在新周期可能叫DPQ010后面跟大写字母。更准确地说同一个“逻辑变量”在不同周期的文件里经常名字完全一样但变量的含义、选项编码甚至取值范围会微调。这也是为什么我强烈建议不要闭着眼睛横向拼接不同周期的同名变量先打开对应周期的Codebook逐列核对一遍。2.2 数据字典里必看的三列我每次清洗前会先下载对应周期的数据字典也就是那个HTML或PDF格式的Codebook。不用从头看到尾重点看三块Variable Description这个变量的判断对象是谁适用年龄段是多少。这里能直接解释掉一大批结构性缺失。Code or Value每个具体取值对应的含义特别是777、999这类特殊值的定义。SAS Label有些变量的Label会把子样本范围说清楚比如“Males aged 2-5 years”这会直接影响你是否该把这个变量放进全样本分析。这一步做完你对整个文件的结构性缺失大概是心里有数的。后面写清洗代码的时候缺失值字典就能直接从Codebook里抄过来。2.3 用pandas读XPT而不是转CSV很多人习惯先把XPT转成CSV再用Excel看我不太推荐。原因有两个一是XPT里有些SAS特殊缺失值在转CSV的过程中会被悄悄抹掉等你读入pandas时已经丢了信息二是直接用pd.read_sas读XPT非常方便读取时把formatxport指对就行。import pandas as pd demo pd.read_sas(DEMO_J.XPT, formatxport) bpx pd.read_sas(BPX_J.XPT, formatxport)读进来的DataFrame列名全部是大写属于正常现象。有的版本里字符列会读成字节串比如显示成bSome Text这时候用df[col] df[col].str.decode(utf-8)处理一下即可。3. 合并数据的正确姿势内连接为主但别忽略重复行3.1 把人口学变量和检查变量拼到一起同周期内的多文件合并核心就是pd.merge主键用SEQN连接方式多数情况用howinner或者howleft取决于你最终的分析人群定义。举个例子如果你想分析的是“有完整人口学信息和血压测量结果的成年人”那就从DEMO_J里筛出RIDAGEYR 20的样本再和BPX_J做内连接。这样出来的行数只会少不会多且每一行都对应一个真实完成过两类检查的受访者。merged pd.merge( demo, bpx, onSEQN, howinner, validateone_to_one )这里有个很实际的坑DEMO在每个周期内是每人一行主键唯一但有些检查文件不一定。比如某些饮食回忆数据在个别周期里做过两次访问SEQN会出现两次还有一些重复测量文件本来就会产生多行。所以合并前一定要先跑一下df[SEQN].duplicated().any()否则合并后的行数会莫名其妙膨胀。3.2 跨周期合并时先统一列名再纵向拼接如果你想做多年份的汇总分析比如把2015-2018这两个周期拼在一起处理方式完全不一样。这个时候不再是横向merge而是“先清洗每个周期再纵向concat”。纵向拼接前有两件必做的事第一把各周期里同一个变量的列名统一最好去掉后缀第二生成一个cycle列记录每行来自哪个周期。否则拼完以后你根本不知道某个样本是哪年的。我一般会先对每个周期的文件做一次标准化的rename再在做完缺失值处理后concat到一起。批量处理时可以直接用glob把多个周期的XPT文件读进来循环但注意每次循环都要先把cycle列填好。4. 缺失值清洗从识别特殊编码到处理低于检出限4.1 把777、888这类特殊值映射成NaN清洗缺失值的第一步就是把Codebook里的特殊数值标记统一转换成np.nan。这个动作不能只做一次而是要把整个文件里的所有变量都过一遍。我习惯的做法是先读取Codebook里列出的特殊编码整理成变量到取值列表的映射字典然后用replace统一替换。import numpy as np special_missing_map { BPXSY1: [777, 888], BPXDI1: [777, 888], # 其他变量按实际Codebook补充 } for col, vals in special_missing_map.items(): if col in merged.columns: merged[col] merged[col].replace(vals, np.nan)需要注意一点并不是所有三位数都是缺失值。有些变量里888可能就是真实合理值全部靠数据字典确认。万一某个变量在Codebook里没写特殊缺失说明那就别动它。4.2 区分“真缺失”和“结构性缺失”这是NHANES清洗里最容易影响分析质量的一步。所谓结构性缺失就是这个人本来就不属于该变量的目标人群比如男性问卷里的怀孕相关变量、20岁以下受访者的成人饮食问题。这类缺失代表的是“不适用”而不是“没测到”。处理结构性缺失时把整个变量置成NaN是可以的但你在做人群筛选时最好直接把人限定在目标子样本里。比如分析血压时先筛掉没有血压读数的人再分析这样剩下的缺失通常就是个别遗漏比例很低。我每次清洗完都会跑一个缺失率报告看看每个核心变量的缺失比例是否合理。如果某个变量缺失率超过50%那大概率是结构性缺失要回头确认合并方式对不对。4.3 实验室数据里的“低于检出限”是另一个世界如果你是做环境暴露、营养生化这类分析NHANES实验室文件里还有一类特别容易踩坑的标记——低于检出限LOD。很多实验室变量并不会直接给你一个浓度值而是给一个类似0.001这种等于或接近检测下限的值同时数据字典里会单独列出这个变量的检出限。很多人看到这些值以为是小到可以忽略的正常值直接拿去做统计分析。这个做法在医学论文审稿人眼里是有问题的。正确的常见做法是按分析目的专门处理比如用LOD除以根号2作为替代值或者用专门处理删失数据的模型。这个已经不是pandas能搞定的范畴了但清洗阶段至少要把这些“低值”识别出来建议单独加一个标记列不要混在日常缺失里面处理。5. 权重与抽样设计变量清洗阶段就要留好5.1 WTMEC2YR、SDMVPSU、SDMVSTRA分别管什么NHANES是复杂抽样设计不是简单随机抽样忽略权重和抽样设计直接跑统计结果很容易偏。好消息是你清洗阶段不需要真的去计算什么只需要确保三个关键列没有被误删WTMEC2YR两年的检查样本权重一般做人口学和体检数据合并分析时用这个权重。SDMVPSU抽样主要抽样单元方差估计要用到。SDMVSTRA分层变量同样是方差估计必备。这三个变量都在DEMO_*.XPT文件里做merge以后它们会自动带进来但如果你做纵向concat千万别顺手当成“冗余列”删掉。后面用survey包或者weights参数建模的时候全指望它们。5.2 跨周期合并后的权重调整如果你把多个两年周期拼在一起当一个大样本那么原来每个样本都带一个“两年权重”WTMEC2YR直接作为最终权重使用是不合适的。因为合并四个周期后样本量大约是单个周期的四倍而每个周期内部的权重却还是按两年设计来计算的。常见的做法是把合并后的权重除以合并的周期数。比如合并两个周期权重因子是1/2合并四个周期权重因子是1/4。具体在模型里要不要再乘以某种缩放常数看你用的统计分析软件的习惯。总之跨周期合并数据绝不能只拼行权重调整必须同步做否则你得到的置信区间和人口代表性都有问题。关于这一点NHANES官方分析指南里有更严谨的论述我建议用到多周期合并时直接去查对应版本的方法学文档比自己琢磨靠谱得多。6. 可直接复用的清洗流水线代码6.1 构建一个2017-2018血压分析数据集的完整代码这一节我直接把上面所有步骤串成一套可运行的流水线。目标是从DEMO_J.XPT和BPX_J.XPT两个文件出发构建一个“20岁及以上成年人、有完整年龄和血压测量”的分析数据集。import pandas as pd import numpy as np # 1. 读取文件 demo pd.read_sas(DEMO_J.XPT, formatxport) bpx pd.read_sas(BPX_J.XPT, formatxport) # 2. 保留核心变量减少内存占用 demo_cols [SEQN, RIAGENDR, RIDAGEYR, RIDRETH1, DMDEDUC2, WTMEC2YR, SDMVPSU, SDMVSTRA] bpx_cols [SEQN, BPXSY1, BPXDI1, BPXPLS] demo demo[demo_cols].copy() bpx bpx[bpx_cols].copy() # 3. 缺失值特殊编码替换按实际Codebook补充 special_missing { BPXSY1: [777, 888], BPXDI1: [777, 888], BPXPLS: [777, 888], } for col, vals in special_missing.items(): if col in bpx.columns: bpx[col] bpx[col].replace(vals, np.nan) # 4. 按分析人群筛选20岁及以上成年人 demo demo[demo[RIDAGEYR] 20].copy() # 5. 横向合并人口学与血压检查数据 merged pd.merge(demo, bpx, onSEQN, howinner, validateone_to_one) # 6. 缺失率检查 missing_report merged.isna().mean().round(4) print(missing_report[missing_report 0]) # 7. 保存清洗后数据 merged.to_csv(nhanes_2017_2018_bp_clean.csv, indexFalse)这段代码跑完你会得到一个主键唯一、包含人口学变量、血压变量和抽样设计变量的干净数据框。如果missing_report里出现某个核心变量缺失比例异常高先回查第2步的字典映射和筛选逻辑。6.2 清洗完之后的四项自检我不止一次见过清洗流程很完整但结果不能用的情况所以最后沉淀一个自检清单每次跑完数据都过一遍行数是否合理成年人样本数量级应该在几千行如果只剩几百行多半是merge时把样本误伤删掉了。SEQN是否唯一merged[SEQN].duplicated().any()必须为False否则纵向拼接或者重复测量文件混入会有问题。核心变量缺失率是否可解释缺失率突然升高时去Codebook里找这个变量的适用人群条件。权重列是否完整WTMEC2YR出现NaN或负数时说明该样本可能来自非检查子样本需要回看合并方式。这套自检流程虽然简单但能拦下绝大多数低级错误。我自己现在处理NHANES数据时已经把“清洗脚本数据字典版本缺失率报告”作为一个整体固化了每次分析完都会同步留档。毕竟NHANES这种大型公共数据库最怕的不是代码写不出来而是清洗出来的数据和原始文档对不上。把这份工作做到规范后面统计分析阶段会省掉大量反复核对的时间。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门