R语言循环脚本优化及起始列调整技术咨询
问题
我有一段可正常运行的R脚本,功能如下:遍历包含分类单元名称的dataframe,为每个分类单元名称查找对应的数值ID;若ID为NA,则保留原dataframe中的名称,最终将结果写入新的dataframe。该脚本可正常工作,但写法较为繁琐,希望获取优化或简化的建议(不强制使用taxize包)。同时想咨询:若使用该脚本(或其他方案),如何将循环的起始列从第2列改为第4列?
示例数据
数据表格
| Kingdom | Phylum | Class | Order | Family | Genus |
|---|---|---|---|---|---|
| Bacteria | Firmicutes | Clostridia | Eubacteriales | Lachnospiraceae | Dorea |
| Bacteria | Firmicutes | Clostridia | Eubacteriales | Oscillospiraceae | GGB9634 |
| Bacteria | Firmicutes | Clostridia | Eubacteriales | Clostridiaceae | Clostridiaceae_unclassified |
数据代码
taxa <- structure(list(Kingdom = c("Bacteria", "Bacteria", "Bacteria" ), Phylum = c("Firmicutes", "Firmicutes", "Firmicutes"), Class = c("Clostridia", "Clostridia", "Clostridia"), Order = c("Eubacteriales", "Eubacteriales", "Eubacteriales"), Family = c("Lachnospiraceae", "Oscillospiraceae", "Clostridiaceae"), Genus = c("Dorea", "GGB9634", "Clostridiaceae_unclassified" )), row.names = c(NA, -3L), class = "data.frame")
现有脚本
sad <- taxa[1:3,] # 取上述dataframe的前3行 numID <- data.frame(sad$Kingdom) # 存储ID的dataframe taxize::taxize_options(ncbi_sleep = 0.9) # 调整HTTP请求频率 for(r in 1:length(sad[,1])){ for(c in 2:length(sad[1,])){ sadID <- taxize::get_uid(sad[r,c], ask=F)[1] if(is.na(sadID)){ numID[r,c] <- sad[r,c] } else { numID[r,c] <- sadID } }} names(numID) <- names(sad) # 期望输出 structure(list(Kingdom = c("Bacteria", "Bacteria", "Bacteria"), Phylum = c("Firmicutes", "Firmicutes", "Firmicutes"), Class = c("186801","186801", "186801"), Order = c("186802", "186802", "186802"), Family = c("186803", "216572", "31979"), Genus = c("189330","GGB9634", "Clostridiaceae_unclassified")), row.names = c(NA,3L), class = "data.frame")
解决方案
一、脚本优化建议
1. 使用dplyr + purrr替代嵌套循环
用mutate(across(...))批量处理指定列,结合map函数实现单值查询,代码更简洁易读:
library(dplyr) library(purrr) library(taxize) taxize_options(ncbi_sleep = 0.9) # 定义查询函数:获取ID,NA则返回原名称 get_taxid_or_name <- function(taxon_name) { taxid <- get_uid(taxon_name, ask = F)[1] ifelse(is.na(taxid), taxon_name, as.character(taxid)) } # 批量处理列(示例默认从第2列开始) numID <- sad %>% mutate(across(2:ncol(.), ~ map_chr(., get_taxid_or_name)))
2. 缓存查询结果减少重复API请求
如果数据中有大量重复的分类单元名称,用memoise包缓存查询结果,避免重复调用API,大幅提升效率:
library(memoise) # 缓存查询函数 memoised_get_taxid <- memoise(get_uid) get_taxid_or_name_cached <- function(taxon_name) { taxid <- memoised_get_taxid(taxon_name, ask = F)[1] ifelse(is.na(taxid), taxon_name, as.character(taxid)) } # 使用缓存函数处理 numID <- sad %>% mutate(across(2:ncol(.), ~ map_chr(., get_taxid_or_name_cached)))
3. 基础R版本优化(不依赖tidyverse)
如果不想用tidyverse包,可以用apply系列函数替代嵌套循环:
library(taxize) taxize_options(ncbi_sleep = 0.9) get_taxid_or_name <- function(x) { taxid <- get_uid(x, ask = F)[1] ifelse(is.na(taxid), x, as.character(taxid)) } # 处理指定列,返回矩阵后转成dataframe processed_cols <- apply(sad[,2:ncol(sad)], c(1,2), get_taxid_or_name) numID <- cbind(sad[,1, drop = FALSE], processed_cols) names(numID) <- names(sad)
二、修改循环起始列为第4列的方法
1. 原脚本修改方式
直接调整内层循环的起始值,同时初始化numID时保留前3列的原始值:
sad <- taxa[1:3,] # 初始化时保留前3列原始数据 numID <- sad[,1:3, drop = FALSE] taxize::taxize_options(ncbi_sleep = 0.9) for(r in 1:nrow(sad)){ # 循环从第4列开始到最后一列 for(c in 4:ncol(sad)){ sadID <- taxize::get_uid(sad[r,c], ask=F)[1] numID[r,c] <- ifelse(is.na(sadID), sad[r,c], sadID) }}
2. 优化后脚本修改方式
不管是tidyverse还是基础R版本,只需修改处理列的范围:
tidyverse版本:
numID <- sad %>% # 处理第4列到最后一列 mutate(across(4:ncol(.), ~ map_chr(., get_taxid_or_name_cached)))
基础R版本:
processed_cols <- apply(sad[,4:ncol(sad)], c(1,2), get_taxid_or_name) numID <- cbind(sad[,1:3, drop = FALSE], processed_cols) names(numID) <- names(sad)
内容的提问来源于stack exchange,提问作者Z U
相关产品推荐
相关产品推荐

