在函数中结合for循环与dplyr的mutate和case_match遇异常
问题:dplyr中for循环结合case_match批量替换文本失效
尝试自定义函数,通过传入存储替换规则的数据框,用dplyr的mutate+case_match实现自动化文本替换。硬编码匹配规则时能得到正确结果,但用for循环动态生成规则后,新列值与原列完全一致,替换未生效。
正确的无循环代码示例
dftest <- data.frame(old = c("ones","twos","fours","fives"), new = c("Humanoid", "Hairy","Hairy","what")) test1 <- data.frame(spp= c("ones", "twos", "threes"), log = c(5,61,36)) updnames <- function(df, col, names_df) { require(dplyr) if(ncol(names_df)>2) {stop("More than 2 columns in names dataframe")} if(sum(duplicated(names_df[1]))>0) {stop("Duplicate old species names")} else { names_df <- names_df %>% mutate_all(as.character) df <- df %>% mutate(updnames = case_match({{col}}, names_df[1,1] ~ names_df[1,2], names_df[2,1] ~ names_df[2,2], names_df[3,1] ~ names_df[3,2], names_df[4,1] ~ names_df[4,2], .default = {{col}}))} return(df) } test2 <- updnames(test1, spp,dftest) # 正确输出 # spp log updnames # 1 ones 5 Humanoid # 2 twos 61 Hairy # 3 threes 36 threes
错误的循环代码示例
updnames <- function(df, col, names_df) { require(dplyr) if(ncol(names_df)>2) {stop("More than 2 columns in names dataframe")} if(sum(duplicated(names_df[1]))>0) {stop("Duplicate old species names")} else { names_df <- names_df %>% mutate_all(as.character) for(i in 1:nrow(names_df)){ df <- df %>% mutate(updnames = case_match({{col}}, names_df[i,1] ~ names_df[i,2], .default = {{col}}))} } return(df) } test2 <- updnames(test1, spp, dftest) # 错误输出 # spp log updnames # 1 ones 5 ones # 2 twos 61 twos # 3 threes 36 threes
问题原因
原循环代码的核心问题是每次迭代都会基于原始列重新生成updnames,前一次循环的替换结果会被后一次覆盖。最后一次循环处理的是fives→what,而测试数据中没有fives,因此最终updnames完全等于原列值,所有之前的替换操作都被清零。
解决方案
方案1:动态构建case_match的匹配规则
利用rlang包的非标准求值工具,动态生成所有匹配规则,无需循环:
library(dplyr) library(rlang) updnames <- function(df, col, names_df) { if(ncol(names_df) > 2) { stop("替换规则数据框不能超过2列") } if(any(duplicated(names_df[[1]]))) { stop("替换规则中的旧名称存在重复") } names_df <- names_df %>% mutate_all(as.character) # 遍历替换规则,生成case_match需要的表达式 match_pairs <- pmap(names_df, ~ expr(!!sym(..1) ~ !!..2)) df %>% mutate(updnames = case_match({{col}}, !!!match_pairs, # 拼接所有匹配规则 .default = {{col}})) } # 测试 test2 <- updnames(test1, spp, dftest) test2
方案2:用左连接实现替换(更简洁高效)
放弃case_match,改用left_join结合coalesce,逻辑更清晰,性能更优:
library(dplyr) updnames <- function(df, col, names_df) { if(ncol(names_df) > 2) { stop("替换规则数据框不能超过2列") } if(any(duplicated(names_df[[1]]))) { stop("替换规则中的旧名称存在重复") } names_df <- names_df %>% mutate_all(as.character) # 统一替换规则的列名,方便连接 colnames(names_df) <- c("target", "new_val") df %>% # 按目标列和替换规则的旧名称连接 left_join(names_df, by = setNames("target", as_name(enquo(col)))) %>% # 优先取替换后的值,无匹配则保留原值 mutate(updnames = coalesce(new_val, {{col}})) %>% # 移除临时列 select(-target, -new_val) } # 测试 test2 <- updnames(test1, spp, dftest) test2
内容的提问来源于stack exchange,提问作者Gesler
相关产品推荐
相关产品推荐

