R语言如何将webchem的PubChem查询结果格式化转为dataframe
webchem包ci_query嵌套结果转dataframe实现方案
ci_query返回结果为三级嵌套结构,按字段类型拆分处理即可,不需要硬转成单张宽表(嵌套的多行tibble硬塞宽表会造成严重数据冗余),以下是可直接复用的代码,兼容3500个CAS号批量查询场景。
单条结果解析逻辑
返回的9个字段分三类处理:
- 单值标量字段(cas、inchi、inchikey、smiles、source_url):每个CAS号对应唯一值,直接提取作为主表列
- 多值向量字段(name、synonyms):每个CAS号对应多个名称/同义词,可先用分隔符合并为单字符串存入主表,需要长表时再拆分
- 嵌套表格字段(toxicity、physprop):本身为多行结构,单独存为带CAS关联键的长表,后续通过CAS号和主表关联即可
先写单条结果解析的容错函数,自动处理查询失败返回空的情况:
library(tidyverse) library(webchem) # 单条结果解析辅助函数 parse_ci_result <- function(ci_res, query_id) { # 查询为空时返回占位结果 if(is.null(ci_res) | length(ci_res) == 0) { return(list( main_info = tibble(cas_query = query_id, query_status = "no_result"), toxicity = tibble(cas = query_id), physprop = tibble(cas = query_id) )) } # 提取单值基础字段 main_single <- tibble( cas_query = query_id, cas = ifelse(is.null(ci_res$cas), NA_character_, ci_res$cas[1]), inchi = ifelse(is.null(ci_res$inchi), NA_character_, ci_res$inchi[1]), inchikey = ifelse(is.null(ci_res$inchikey), NA_character_, ci_res$inchikey[1]), smiles = ifelse(is.null(ci_res$smiles), NA_character_, ci_res$smiles[1]), source_url = ifelse(is.null(ci_res$source_url), NA_character_, ci_res$source_url[1]), query_status = "success" ) # 合并多值向量字段,用|做分隔符 main_multi <- tibble( cas_query = query_id, common_names = ifelse(is.null(ci_res$name), NA_character_, paste(ci_res$name, collapse = "|")), synonyms = ifelse(is.null(ci_res$synonyms), NA_character_, paste(ci_res$synonyms, collapse = "|")) ) main_info <- main_single %>% left_join(main_multi, by = "cas_query") # 处理嵌套毒性表,加CAS关联键 toxicity_tab <- if(is.null(ci_res$toxicity) | nrow(ci_res$toxicity) == 0) { tibble(cas = ifelse(is.null(ci_res$cas), query_id, ci_res$cas[1])) } else { ci_res$toxicity %>% mutate(cas = ifelse(is.null(ci_res$cas), query_id, ci_res$cas[1]), .before = 1) } # 处理嵌套理化性质表,加CAS关联键 physprop_tab <- if(is.null(ci_res$physprop) | nrow(ci_res$physprop) == 0) { tibble(cas = ifelse(is.null(ci_res$cas), query_id, ci_res$cas[1])) } else { ci_res$physprop %>% mutate(cas = ifelse(is.null(ci_res$cas), query_id, ci_res$cas[1]), .before = 1) } return(list( main_info = main_info, toxicity = toxicity_tab, physprop = physprop_tab )) } # 单条测试 y1 <- ci_query('50-00-0', from = 'rn') parsed_y1 <- parse_ci_result(y1[['50-00-0']], query_id = '50-00-0')
运行后得到三个结构化对象:
parsed_y1$main_info:1行的化合物基础信息宽表,每个CAS号对应一行parsed_y1$toxicity:毒性数据长表,每条毒性测试记录对应一行,带CAS号做关联parsed_y1$physprop:理化性质长表,每条理化指标对应一行,带CAS号做关联
批量处理3500个CAS号的实现
直接全量循环请求容易被站点限流、中途报错中断,以下代码加了随机停顿、错误捕获、断点续跑逻辑,中途断网/报错后重新运行不会重复请求已经抓取过的条目:
# 替换为你自己的CAS号向量 # cas_list <- c("50-00-0", "67-64-1", ......) # 初始化存储列表,支持断点续跑 if(!exists("all_raw_res")) { all_raw_res <- list() } # 批量循环查询 for(cas in cas_list) { # 已查询过的条目直接跳过 if(cas %in% names(all_raw_res)) next tryCatch({ res <- ci_query(cas, from = "rn", verbose = FALSE) all_raw_res[[cas]] <- res[[cas]] # 打印进度 cat("Finished:", cas, "Progress:", round(length(all_raw_res)/length(cas_list)*100, 2), "%\n") # 随机停顿0.5-2秒,避免被限流 Sys.sleep(runif(1, 0.5, 2)) }, error = function(e) { cat("Error querying:", cas, "\n") all_raw_res[[cas]] <<- NULL }) } # 批量解析所有结果 all_parsed <- map2(all_raw_res, names(all_raw_res), ~parse_ci_result(.x, .y)) # 合并为最终三个规范dataframe main_df <- map_dfr(all_parsed, ~.x$main_info) toxicity_df <- map_dfr(all_parsed, ~.x$toxicity) physprop_df <- map_dfr(all_parsed, ~.x$physprop)
补充说明
ci_query默认抓取ChemIDplus数据库数据,并非PubChem数据源。如果需要PubChem数据,将查询函数替换为同包的pc_query即可,上述解析逻辑完全通用,仅需对应调整字段名匹配返回结构。- 如果需要把常用名、同义词拆分为每行一个值的长格式,调用
tidyr::separate_longer_delim(main_df, cols = c(common_names, synonyms), delim = "|")即可快速实现。 - 不要强行将毒性、理化性质两类多行数据合并到基础信息主表,会导致单CAS号对应多行、基础字段大量冗余,大幅提升后续数据处理的出错概率,使用时通过CAS号关联三张表即可。
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

