dplyr中across内eval与parse求值问题及列类型转换报错排查
问题分析与解决
背景
我在dplyr的代码仓库提交了issue,官方表示across并不适用于这类匹配列名并更新列类型的场景,但我还是想搞懂现有代码报错的原因。我的需求是写一个函数,通过匹配列名,用数据框y的列类型来更新数据框x的对应列类型,之前能用的代码现在报错了。
示例数据
library(tidyverse) # 数据框x,所有列都是字符型 (x = tibble(data.frame(a = as.character(c(1,2,3,4)), b = as.character(c(1,2,3,4)), c = as.character(c(1,2,3,4)), d = as.character(c('a', 'b', 'c', 'd'))))) #> # A tibble: 4 × 4 #> a b c d #> <chr> <chr> <chr> <chr> #> 1 1 1 1 a #> 2 2 2 2 b #> 3 3 3 3 c #> 4 4 4 4 d # 数据框y,列类型包含数值型和字符型 (y = tibble(data.frame(a = as.numeric(c(1,1,1,1)), b = as.character(c(1,1,1,1)), c = as.numeric(c(1,1,1,1)), d = as.character(c('a', 'a', 'a', 'a'))))) #> # A tibble: 4 × 4 #> a b c d #> <dbl> <chr> <dbl> <chr> #> 1 1 1 1 a #> 2 1 1 1 a #> 3 1 1 1 a #> 4 1 1 1 a
报错代码及错误信息
# 函数内的这段代码报错 result <- x %>% dplyr::mutate( dplyr::across( .cols = tidyselect::all_of( colnames( y ) ), .fns = ~eval(parse(text = paste0( "as.", class( y[[dplyr::cur_column()]] ), "(.)" )))))
错误信息:
Error in `dplyr::mutate()`: ! Problem while computing `..1 = dplyr::across(...)`. Caused by error in `across()`: ! Problem while computing column `a`. Caused by error: ! 'list' object cannot be coerced to type 'double' Backtrace: ▆ 1. ├─x %>% ... 2. ├─dplyr::mutate(...) 3. ├─dplyr:::mutate.data.frame(...) 4. │ └─dplyr:::mutate_cols(.data, dplyr_quosures(...), caller_env = caller_env()) 5. │ ├─base::withCallingHandlers(...) 6. │ ├─base::withCallingHandlers(...) 7. │ └─mask$eval_all_mutate(quo) 8. ├─base::eval(...) 9. │ └─base::eval(...) 10. └─base::.handleSimpleError(...) 11. └─dplyr (local) h(simpleError(msg, call)) 12. └─rlang::abort(msg, call = call("across"), parent = cnd)
期望结果
result = data.frame(a = as.numeric(c(1,2,3,4)), b = as.character(c(1,2,3,4)), c = as.numeric(c(1,2,3,4)), d = as.character(c('a', 'b', 'c', 'd')))
报错原因
核心问题出在class(y[[dplyr::cur_column()]])的返回值上:因为y是tibble,用y[[col]]提取列时,class()函数返回的是类型向量列表(比如数值型列会返回c("numeric", "double"))。
当把这个列表拼接到as.后面时,生成的字符串会变成as.numericdouble(.),这不是合法的函数调用,最终导致eval解析时出错,抛出“list对象无法转为double”的错误。
另外,across的运行环境有特殊性:在.fns里调用cur_column()时,y[[cur_column()]]的查找上下文和预期不一致,进一步加剧了问题。
解决方法
不用eval(parse())这种绕弯的方式,直接匹配列名、提取y的列类型,再用对应转换函数处理x的列即可,以下是两种可靠方案:
方案1:结合across与do.call处理
# 只处理x和y共有的列 common_cols <- intersect(colnames(x), colnames(y)) result <- x %>% mutate(across(all_of(common_cols), ~{ # 取类型向量的第一个元素作为目标类型 col_type <- class(y[[cur_column()]])[1] do.call(paste0("as.", col_type), list(.x)) }))
方案2:直接遍历列名处理
如果觉得across的环境问题麻烦,也可以不用across,直接循环处理:
common_cols <- intersect(colnames(x), colnames(y)) result <- x for (col in common_cols) { col_type <- class(y[[col]])[1] result[[col]] <- do.call(paste0("as.", col_type), list(result[[col]])) }
两种方案都能得到预期结果,且避免了eval(parse())带来的解析错误和across的环境问题。
内容的提问来源于stack exchange,提问作者Voy
相关产品推荐
相关产品推荐

