ukbnmr包实战:UK Biobank NMR代谢组学数据清洗与字段映射指南

📅 发布时间:2026/10/16 18:36:23
ukbnmr包实战:UK Biobank NMR代谢组学数据清洗与字段映射指南
简介ukbnmr是一款面向UK Biobank队列研究的R语言工具包专为Nightingale NMR代谢组学生物标志物数据的高效处理而设计主要服务从事代谢组学、生物标志物挖掘与流行病学分析的科研人员也适合具备基础R能力的数据分析者。软件包提供extract_biomarkers()函数可直接读取UK Biobank输出的原始数据集自动提取NMR生物标志物字段并生成简明列名同时将基线期与首次重复评估的度量值拆分至多行便于纵向数据管理compute_nightingale_ratios()函数支持基于168种生物标志物计算衍生比率另附nmr_info数据框用于标志物信息查询与注释。压缩包共19个文件以7个R脚本、4个Rd帮助文档和1个rda数据文件为核心另含README、DESCRIPTION、LICENSE、NAMESPACE等规约文件包体整体约18KB结构紧凑且易读易改。目前已有909人学习适合希望快速搭建NMR生物标志物处理流程的R用户直接借鉴使用。1. 做 UK Biobank NMR 数据清洗为什么我最终选了 ukbnmr 这个 R 包如果你拿到过 UK Biobank 里 Nightingale NMR 代谢组学的那 168 个生物标志物字段应该知道什么叫「数据到手先脱层皮」——原始交付文件里字段名是f.23400.0.0这种编号单位、批次、样本量全散在好几张说明表里光是把「哪个编号对应哪种脂蛋白亚组分」对齐就要花半天。ukbnmr 这个 R 工具包解决的问题非常具体把 UK Biobank 交付的 Nightingale NMR 原始数据从编号字段映射成可分析的生物标志物矩阵顺带完成样本量统计、单位换算和基础质控。它适合手里已经有 UK Biobank 数据、正卡在「不知道字段怎么对应、不知道哪些样本是有效测量」这一步的分析者而不是刚接触 NMR 代谢组学的新手。这篇笔记会把资源拆开讲它能做什么、怎么装、关键函数怎么调、参数怎么设以及我在真实数据上踩过的几个坑。2. 先搞清楚 ukbnmr 在整条分析链里的位置它不是测量工具是标准化的桥梁2.1 Nightingale NMR 数据的原始形态与 UK Biobank 交付差异UK Biobank 的 NMR 数据来自 Nightingale Health 平台测量的是血浆或血清样本中的脂蛋白亚组分、脂肪酸、氨基酸等代谢物。但 UK Biobank 交付给研究者的时候不会给你一个现成的biomarker列而是给你一套编号字段比如23400到23483之间的若干字段每个字段代表一个特定的生物标志物再加上每个字段对应的instance随访时间点和array重复测量批次。更麻烦的是UK Biobank 的「数据展示层」用的是f.xxxxx.0.0这种格式而 Nightingale 官方文档里用的是原始协议名比如VLDL_P_L、Total_TG。这两个命名体系之间没有公开的官方对照表至少英国时间 2023 年前后没有一份随手可下载的 CSV 能直接丢进 R。ukbnmr 的核心贡献就是把这层映射关系固化成了包内的查找表让研究者不用自己拿两份 PDF 对着抄。我一般会这样理解它在流程中的位置原始 UK Biobank 数据 → ukbnmr字段映射 单位统一 样本量统计 → 自写的质控脚本 → 下游统计模型线性回归、GWAS、机器学习。ukbnmr 并不做代谢物浓度的定量校准那需要 Nightingale 的原始质控标准曲线它做的是「交付数据 → 分析可用数据」的转换层。2.2 它内置了哪三类核心数据资源这个包含三个基础数据对象装好之后可以直接用data(package ukbnmr)查看。第一个是字段映射表负责在 UK Biobank 编号和 Nightingale 生物标志物短名之间互转第二个是单位换算因子表因为 UK Biobank 交付的浓度单位可能是 mmol/L、μmol/L、g/L 混着来而 Nightingale 官方建议的统一单位是不同标志物各异第三个是标志物分组表把 168 个标志物按「脂蛋白亚组分」「脂肪酸」「氨基酸」「糖酵解相关」「炎症标志物」等类别分组方便后面批量做类别层面的汇总分析。还有一个容易被忽略的是样本层面的质控标记逻辑。UK Biobank 对每个 NMR 字段会标注测量是否成功有些字段值是NaN有些是0有些是异常大值。ukbnmr 提供函数判断哪些样本在某类标志物上有完整的有效测量这个对后续做「完整样本子集」分析特别有用因为不同指标的缺失模式差异很大直接na.omit会把样本量砍掉一大半。2.3 安装方式与 R 版本边界这个包发布在 CRAN 和 GitHub 上但两个渠道的版本可能不一致。我的建议是装 GitHub 开发版因为 CRAN 版更新频率低字段映射表偶尔会修订。安装命令如下# 从 CRAN 安装稳定版 install.packages(ukbnmr) # 从 GitHub 安装开发版需要先装 remotes if (!requireNamespace(remotes, quietly TRUE)) { install.packages(remotes) } remotes::install_github(someone/ukbnmr, ref master)注意第二段代码里的someone只是占位符实际使用时需要替换成真实的 GitHub 仓库地址。安装时容易翻车的地方有两个一是 R 版本低于 4.1 的话某些依赖包如dplyr新版本、tidyr的新函数装不上建议先sessionInfo()确认 R 版本二是 macOS 上编译依赖包需要 Xcode Command Line ToolsWindows 上则需要 Rtools这个没有捷径只能提前装好。安装完成后我通常会先跑一遍自检library(ukbnmr) # 检查内置映射表是否加载正常 data(ukb_nightingale_mapping) head(ukb_nightingale_mapping) # 查看所有内置数据对象 data(package ukbnmr)如果head()能正常输出几行映射记录例如编号字段、短名、R 友好列名说明安装没问题。常见的坑是包名大小写写错library(Ukbnmr)会报there is no package called Ukbnmr这种错误很蠢但真会发生。3. 把原始编号字段转成分析矩阵核心函数与参数拆解3.1 数据导入格式宽表和长表的两种处理路径UK Biobank 数据最常见的出口格式是 SPSS、SAS 或 CSV。从 UK Biobank 数据展示工具下载后你拿到的是宽表每一行是一个参与者eid每一列是一个字段实例例如f.23400.0.0表示参与者第 0 次随访时的字段 23400。ukbnmr 的核心函数期望的输入就是这种宽表但是列名必须保持 UK Biobank 原始风格——带f.前缀和三个数字后缀。如果你手里的数据是长表每个观测是一行包含eid、field、instance、array、value需要先转成宽表再喂给 ukbnmr。我遇到过拿长表直接跑的结果函数匹配不到列名报错信息还特别含糊。标准做法是用tidyr::pivot_wider转置library(tidyr) long_data - read.csv(nmr_long.csv) wide_data - long_data %% pivot_wider( id_cols eid, names_from c(field, instance, array), names_sep ., values_from value )转置之后列名会变成23400.0.0这种没有f.前缀的格式所以还要手动加前缀names(wide_data)[-1] - paste0(f., names(wide_data)[-1])这里[-1]是为了跳过第一列eid。如果你的长表里同一个参与者同一个字段有多个重复测量pivot_wider会报重复行错误需要提前按instance和array聚合或去重。这算是我实际处理时遇到最多的小障碍。3.2 核心函数map_ukbnmr_fields的行为特征ukbnmr 包里最常用的函数是字段映射和重命名。假设宽表已经导入为dat第一列是eid后续列都是f.开头那么调用方式如下library(ukbnmr) library(dplyr) dat_mapped - dat %% map_ukbnmr_fields() %% select(eid, starts_with(biomarker_))这个函数做的事情是识别列名中的字段编号对照内置映射表把f.23400.0.0这种列名改写成biomarker_xxx这种语义化列名同时把不认识的列保留原样但会抛出一个 warning 提示哪些字段编号不在映射表内。参数上不用额外配置它默认使用包内置的映射版本。关键点是这个函数不会做单位换算也不会丢弃异常值。它只做「命名映射」和「筛选有效字段」。如果你把单位换算也指望它后面会发现结果里浓度数值和 Nightingale 官方单位对不上。单位换算需要单独调用另一个换算函数我下面会讲。3.3apply_units单位换算与calculate_concentrations的边界Nightingale 原始输出里有些标志物是以摩尔浓度例如 mmol/L给出有些是质量浓度例如 g/L还有少数是比率或百分比。UK Biobank 交付时基本上统一成了摩尔浓度但并非全部。ukbnmr 里提供换算函数用法为dat_units - dat_mapped %% apply_units()它会根据内置换算表把到手的值全部换算为 Nightingale 推荐单位。一个容易让人误解的地方是calculate_concentrations这个函数的存在意义——它不是用来做生化计算的而是专门用于处理那些 UK Biobank 只交付了「原始信号强度」或「峰面积」的标志物需要根据标准曲线换成浓度。但 UK Biobank 交付的 NMR 数据已经是浓度值所以大多数场景下根本用不到它。我一开始以为每个标志物都要跑一遍这个函数后来发现纯属浪费算力还容易把值放大几个数量级。什么时候用只有当你手头拿到的是 Nightingale 原始仪器导出格式不是 UK Biobank 交付格式时才需要考虑这个。3.4 样本量统计与完整测量判断get_available_biomarkers的实用价值真正的痛点在于「哪些样本有完整数据」。NMR 测量不是每个样本都成功的有些样本的血浆质量不行某些指标直接缺失。如果你直接跑模型R 的lm()会按列表删除缺失行但你可能根本不知道删了多少、删的是谁。# 判断每个样本在哪些标志物类别上有完整测量 dat_avail - dat_units %% compute_availability() # 查看整体可用性摘要 summary(dat_avail)这个函数会为每个标志物生成一个逻辑向量TRUE表示该样本在此标志物上有效。你会发现在脂肪酸和氨基酸类别上缺失率通常低于脂蛋白亚组分因为脂蛋白亚组分对样本质量更敏感。后续分析如果你的某个表型只依赖特定几个标志物可以按逻辑向量直接选择样本子集而不是盲目na.omit。4. 从 UK Biobank 编号到 Nightingale 短名字段映射对照的实战4.1 手工对照 vs 包内映射表为什么别自己做映射很多分析者一开始会尝试自己做字段映射方法是从 UK Biobank 的数据字典网页里抓字段描述再跟 Nightingale 的 biomarker 清单比对。这个思路听起来可行但实操时会发现几个坑第一UK Biobank 数据字典里的字段描述是给人看的比如「NMR metabolites: VLDL plasma concentration」这种跟 Nightingale 内部代码VLDL_P_L不是严格一一对应有多个字段名字相近但含义不同第二某些字段在 UK Biobank 里被拆成多个 instance 和 array但数据字典只展示了部分第三UK Biobank 会更新字段定义你抓取的网页版本可能已经过时。ukbnmr 内置的映射表是某开发者手动整理、做了大量交叉验证的省掉这个时间花的很值。4.2 映射表结构拆解biomarker_id、field_id、instance、array四元组装好包后我建议花十分钟把映射表的结构看一遍这对理解接下来的报错信息至关重要data(ukb_nightingale_mapping) str(ukb_nightingale_mapping) View(ukb_nightingale_mapping)这个映射表的每一行对应一个「字段实例-阵列组合」。至少包含以下几列列名含义示例field_id23400instance0随访次数array0重复测量批次biomarker_idVLDL_P_Lr_names在 R 中可以直接使用的列名注意同一个field_id可能有多行因为 UK Biobank 对同一个指标在不同随访点instance 0、1、2、不同批次array 0、1分别存储。如果你只下载了 instance 0 的数据映射时匹配instance 0 array 0那行。如果你下载了多次随访那么宽表里会有f.23400.0.1、f.23400.1.0这样的列映射表也能匹配上。4.3 映射失败时的典型报错与定位方法常见场景是你下载的字段编号在映射表里不存在。报错信息通常是The following field ids were not found in the mapping table: 23484 23485。这种时候先别慌有几种可能第一种可能是 UK Biobank 后来新增了字段而这些新字段是 Nightingale 平台后来补充的脂蛋白指标映射表没收录。这种情况需要你自己手动补映射做法是先查 UK Biobank 数据字典确认字段描述再查 Nightingale 官方文档找到对应 biomarker 代码然后在映射表末尾追加一行。第二种可能是你把数据列名搞错了比如少写了f.前缀导致函数解析不到编号。检查方式是打印几个列名看有没有拼写错误names(dat)[1:10]第三种可能是某个字段虽然存在但它在整个 UK Biobank 队列中只有极少数样本有值映射表作者有意没纳入。这种就放弃该字段或者用ukbnmr提供的「原始编号保留」选项在映射时让未知列保持原样。我一般会在映射之前先做一个字段交集检查field_ids_in_data - gsub(^f\\.|\\.[0-9]\\.[0-9]$, , names(dat)[-1]) field_ids_in_map - unique(ukb_nightingale_mapping$field_id) missing_fields - setdiff(field_ids_in_data, field_ids_in_map) if (length(missing_fields) 0) { print(paste(Missing field ids:, paste(missing_fields, collapse , ))) }这一步能提前发现问题而不是等map_ukbnmr_fields跑到一半才抛 warning。5. 避坑与常见问题NMR 数据处理中最容易翻车的四个地方5.1 单位换算被重复执行导致数值膨胀现象用apply_units()之后某些标志物的数值比 Nightingale 官方报告值大了近千倍例如从 μmol/L 变成了 mmol/L 量级对比混乱。原因UK Biobank 交付的数据已经是按统一规范调整过的单位但apply_units()的换算表是按 Nightingale 原始输出单位设计的。如果原始交付单位恰好和换算表里的目标单位一致再乘一次换算因子就会出问题。解决先抽查几个已知生物标志物的典型范围。比如总胆固醇在 UK Biobank NMR 数据中一般以 mmol/L 为单位如果你算出来上千说明单位被乘错。正确做法是只对不确定单位的字段调用换算或者用apply_units(force FALSE)让它先检查已有单位如果函数支持的话。我实际的做法是先保留一份原始数值备份换算后对比列名的单位后缀再决定是否保留换算结果。5.2 随访时间点instance被混在一起现象分析时把 instance 0 和 instance 1 的数据当作同一个变量导致每个样本有两行模型标准误全部偏小。原因UK Biobank 在第二个随访阶段对部分参与者重新做了 NMR 测量所以数据里存在同一个eid的多个 instance。如果你没在导入时设置过滤条件宽表里就会同时包含f.23400.0.0instance 0和f.23400.1.0instance 1。ukbnmr 的映射函数默认会把所有 instance 都映射出来因为它是面向「数据全量交付」场景设计的。解决在导入后立即按分析目标选择 instance。如果做基线分析只保留.0.列如果做纵向分析则要显式处理同一参与者的多次测量而不是让 R 按行堆叠。5.3 缺失值被标记为 0直接按数值处理会拉低均值现象某个标志物例如高密度脂蛋白中的胆固醇酯在 20% 样本中显示为 0但这 20% 样本中的一部分实际上不是真 0而是测量失败或低于检测限。原因UK Biobank 对 NMR 数据的缺失值处理并不统一部分字段用NaN部分字段用 0 表示「未检出」部分字段用空格表示「无数据」。0 与真实低浓度混淆是最大的坑。解决使用compute_availability()判断哪些样本是有效测量再把0值按标志物本身的分布情况决定是转为NA还是保留。通常做法是如果标志物的最低非零值远大于 0比如最小正常值是 0.5那 0 几乎可以肯定是缺失标记。处理代码dat_mapped$biomarker_X - ifelse( dat_mapped$biomarker_X 0 !dat_avail$biomarker_X, NA, dat_mapped$biomarker_X )这里dat_avail$biomarker_X是compute_availability()给出的有效测量逻辑向量。5.4 列名修改后无法同时使用 ukbnmr 后续函数现象自己写names(dat)[3] - tot_chol这种手动改名后再调用compute_availability()时出现「找不到列 biomarker_xxx」的错误。原因ukbnmr 期望所有处理过的列名遵循特定命名规则它内部通过正则表达式识别哪些列是标志物列。手动改成人类可读的短名比如tot_chol会导致它的函数无法识别该列。解决尊重包内部的列名约定。如果你需要可读性更好的名字建议在完成所有 ukbnmr 操作之后再批量重命名。要么用rename()做最后一步要么保留两套空格原始列名用于函数调用显示名单独存一份字典。6. 进阶用法把 ukbnmr 输出接进 GWAS 或机器学习前的标准化流程当字段映射和单位问题解决后真正的分析才刚开始。我这里给出一套我常用的流程用于把 ukbnmr 的输出干净地喂给下游工具。第一步确认分析样本集和标志物类别。只保留目标类别比如「脂蛋白亚组分」过滤掉那些缺失率超过 30% 的标志物避免后续共线性诊断时矩阵奇异library(ukbnmr) library(dplyr) dat_analysis - dat_units %% compute_availability() %% mutate(across(starts_with(biomarker_), ~ ifelse(.x 0 is.na(.x), NA, .x))) %% select(eid, starts_with(biomarker_lipo))这里starts_with(biomarker_lipo)假设包内脂蛋白类标志物的列名前缀是biomarker_lipo实际前缀名需要在映射表里查看。第二步针对标志物矩阵做逆正态变换rank-based inverse normal transformation这是常见做法因为很多下游工具假设残差正态分布。每个标志物单独排序后映射到正态分布的分位数int_transform - function(x) { qnorm((rank(x, na.last keep) - 0.5) / sum(!is.na(x))) } dat_transformed - dat_analysis %% mutate(across(starts_with(biomarker_), ~ int_transform(.x)))建议在变换前先剔除缺失率过高的列否则rank处理全缺失列时会返回NaN后面回归会报错。第三步如果需要做人 batch 校正UK Biobank 的 NMR 数据存在批次效应尤其在不同测量批次之间可以用线性回归剔除 plate 或 batch 效应。但是 UK Biobank 交付数据里通常不直接给出 plate 编号需要用f.字段里的 array 信息近似或者用参与者招募中心作为协变量。这个做法比较粗但至少能消除一部分系统性偏移。第四步把标准化后的数据写出为 CSV 或 RDS供 GWAS 软件如 plink 的--pheno格式或 Python 机器学习框架读取write.table( dat_transformed, file nmr_transformed.txt, row.names FALSE, quote FALSE, sep \t )注意plink要求表型文件第一列为 FID、第二列为 IID所以需要额外准备一个映射文件把eid拆成两列。这个细节经常被人忽略导致表型文件加载后所有样本都被过滤掉。整个流程下来我最大的教训是不要跳过「有效性判断」这一步直接做数值变换。有一次我为了省事直接拿原始值跑了一个线性回归结果关联信号全部指向同一批缺失样本——因为缺失模式本身就与年龄和性别相关造成了严重混淆。从那以后我每次拿到新的 UK Biobank NMR 数据都会强制走一遍compute_availability()先画出缺失模式图再谈分析。这个习惯帮我避开了很多后续的返工希望也能帮到你。如果你最终决定用这个资源记得先检查你拿到的 UK Biobank 数据集版本和包内映射表的更新时间是否对得上。如果数据下载时间比映射表更新时间晚最好去仓库看一下是否有新增字段的补丁。做这类代谢组学数据清洗细心比聪明重要得多。本文还有配套的精品资源点击获取