使用dplyr::mutate做条件类型转换时遇'promise already under evaluation'错误
尝试用dplyr::mutate将字符变量转为数值变量时触发未知错误,需求如下:
现有数据框包含两个字符列,一列始终可转为整数,另一列含非法值"E",需将两列转为数值并把"E"替换为0。
示例数据:
df <- data.frame( always_numstring=c("4", "1", "3"), usually_numstring=c("4", "1", "E"))
以下代码正常运行:
df %>% mutate(x=ifelse(always_numstring=="E", 0, as.numeric(always_numstring)))
输出:
always_numstring usually_numstring x 1 4 4 4 2 1 1 1 3 3 E 3
但处理含"E"的列时报错:
df %>% mutate(x=ifelse(usually_numstring=="E", 0, as.numeric(usually_numstring)))
错误信息:
Error in base::nchar(wide_chars$test, type = "width") : promise already under evaluation: recursive default argument reference or earlier problems?
单独执行as.numeric("E")仅产生警告,非dplyr版本可正常运行(仅警告):
df$x <- ifelse(df$usually_numstring=="E", 0, as.numeric(df$usually_numstring))
警告信息:
In ifelse(df$usually_numstring == "E", 0, as.numeric(df$usually_numstring)) : NAs introduced by coercion
输出:
always_numstring usually_numstring x 1 4 4 4 2 1 1 1 3 3 E 0
1. 如何在dplyr中正确实现:检查字符串非法值并按需转换为数值?
推荐几种简洁可靠的方法:
方法1:使用dplyr::if_else(区别于base的ifelse)
dplyr::if_else是类型安全的矢量化条件函数,能避免baseifelse的求值冲突问题:
library(dplyr) df %>% mutate(x = if_else(usually_numstring == "E", 0L, as.integer(usually_numstring)))
方法2:先转数值再替换NA
利用as.numeric将"E"转为NA,再用coalesce把NA替换为0:
df %>% mutate(x = coalesce(as.numeric(usually_numstring), 0))
方法3:使用case_when
适合多条件场景,逻辑更清晰:
df %>% mutate(x = case_when( usually_numstring == "E" ~ 0, TRUE ~ as.numeric(usually_numstring) ))
2. 为何转换"E"为数值在dplyr中会触发致命错误?
核心原因是base::ifelse的矢量化求值特性与dplyr的非标准计算(NSE)机制冲突:
baseifelse会提前计算yes和no分支的所有元素,包括对"E"执行as.numeric生成NA;而dplyr在处理mutate表达式时,会使用延迟求值的promise对象,当NA与dplyr内部的字符串处理逻辑(如调用nchar)交互时,触发了递归求值的错误。
3. 为何会出现该特定错误信息?
错误信息promise already under evaluation说明dplyr内部的延迟求值对象(promise)被重复调用,导致递归引用。具体来说,当baseifelse强制求值as.numeric(usually_numstring)时,dplyr还未完成该变量的环境解析,进而在内部调用nchar处理字符串时,触发了递归求值的冲突。
4. 为何ifelse()会同时计算yes和no表达式?我原以为它和if()行为一致。
两者核心逻辑完全不同:
if()是标量条件判断,仅根据单个布尔值选择执行对应的分支,不会执行未满足条件的分支;ifelse()是矢量化函数,为了处理向量输入,会先完整计算yes和no两个表达式的所有元素,再根据条件向量选择对应位置的值。这就是为什么即使条件为TRUE(对应"E"的行),as.numeric(usually_numstring)依然会被求值,生成NA。
内容的提问来源于stack exchange,提问作者octern

