R语言mutate中case_when函数报错问题排查与修复
问题分析与修复方案
问题背景
现有result数据框结构如下:
| MANUAL.ID | AUTO.ID | loc |
|---|---|---|
| NA | PYPPYP | L2 |
| PIPpip | NA | L1 |
| Barbar | NA | L5 |
| NA | Pippip | L3 |
| NA | Pippip,BerBar | L3 |
需将不同形式的标签替换为标准形式,依赖tesaurus词表(tag_id为待替换标签,tag_ok为标准标签):
| tag_id | tag_ok |
|---|---|
| PYPPYP | Pippip |
| PIPpip | Pippip |
| Pippip | Pippip |
| Barbar | Barbar |
编写的R代码运行报错,错误信息如下:
Error in `mutate()`: ℹ In argument: `AUTO.ID = (structure(function (..., .x = ..1, .y = ..2, . = ..1) ...`. Caused by error in `case_when()`: ! Can't combine `..1 (right)` <character> and `..161 (right)` <double>. --- Backtrace: ▆ 1. ├─result %>% ... 2. ├─dplyr::mutate_at(...) 3. │ ├─dplyr::mutate(.tbl, !!!funs) 4. │ └─dplyr:::mutate.data.frame(.tbl, !!!funs) 5. │ └─dplyr:::mutate_cols(.data, dplyr_quosures(...), by) 6. │ ├─base::withCallingHandlers(...) 7. │ └─dplyr:::mutate_col(dots[[i]], data, mask, new_columns) 8. │ └─mask$eval_all_mutate(quo) 9. │ └─dplyr (local) eval() 10. ├─`<inln_cl_>`(AUTO.ID) 11. │ └─rlang::eval_bare(`_quo`, base::parent.frame()) 12. ├─rlang (local) case_when(...) 13. └─dplyr::case_when(...)
错误原因
- 类型不兼容:
case_when要求所有分支返回的数据类型一致,报错显示部分分支返回字符型、部分返回数值型,大概率是tesaurus的tag_ok列混入了数值类型,或生成的表达式中某条规则的tag_ok未被正确转义为字符。 - 多标签匹配失效:原代码用
str_detect匹配整个字符串,无法处理AUTO.ID中用逗号分隔的多标签(如Pippip,BerBar)。 - 语法兼容性问题:
mutate_at属于dplyr旧版语法,且通过as.formula拼接case_when表达式的方式易出现语法错误,若tag_id包含正则特殊字符(如.、*),会直接导致匹配失效。
修复方案
方法一:兼容单/多标签的通用替换(推荐)
利用stringr::str_replace_all结合命名向量实现批量替换,同时支持多标签场景:
library(dplyr) library(stringr) library(openxlsx) # 读取并整理词表:生成忽略大小写的正则规则,去重避免重复替换 tesaurus <- read.xlsx("Requested_files/sp_tesaurus.xlsx", sheet = "tesaurus") %>% select(tag_id, tag_ok) %>% # 转义正则特殊字符,添加单词边界避免部分匹配 mutate(tag_regex = str_c("\\b", str_replace_all(tag_id, regex("([.+*?^$(){}|\\[\\]\\\\])"), "\\\\\\1"), "\\b")) %>% distinct(tag_regex, tag_ok, .keep_all = TRUE) # 构建命名向量:键为正则表达式,值为标准标签 replace_rules <- set_names(tesaurus$tag_ok, tesaurus$tag_regex) # 对所有含ID的列执行替换,忽略大小写 result <- result %>% mutate(across(contains("ID"), ~ str_replace_all(., regex(names(replace_rules), ignore_case = TRUE), replace_rules)))
方法二:修正原case_when逻辑(仅适用于单标签)
若仅处理单标签场景,可修正原代码的类型与语法问题:
library(dplyr) library(stringr) library(openxlsx) tesaurus <- read.xlsx("Requested_files/sp_tesaurus.xlsx", sheet = "tesaurus") %>% select(tag_id, tag_ok) %>% # 强制转换tag_ok为字符型,避免类型冲突 mutate(tag_ok = as.character(tag_ok)) # 生成case_when规则字符串,转义特殊字符与单引号 case_rules <- paste( sprintf("str_detect(., regex('%s', ignore_case = TRUE)) ~ '%s'", str_replace_all(tesaurus$tag_id, regex("(['\\\\])"), "\\\\\\1"), str_replace_all(tesaurus$tag_ok, "'", "''")), collapse = ", " ) case_expr <- as.formula(paste0("~ case_when(", case_rules, ", TRUE ~ as.character(.))")) # 使用新版across替代mutate_at result <- result %>% mutate(across(contains("ID"), case_expr))
关键说明
- 正则中的
\\b用于匹配完整单词,避免部分匹配(如防止把Barbarian中的Barbar误替换)。 - 转义
tag_id中的正则特殊字符,防止匹配逻辑出错。 across是dplyr新版语法,替代旧版mutate_at,兼容性与可读性更强。
内容的提问来源于stack exchange,提问作者lobarth
相关产品推荐
相关产品推荐

