在dplyr::mutate中基于字符串组装表达式实现条件求值的问题
解决dplyr::mutate()结合case_when时字符串转表达式的问题
你遇到的核心问题是把字符串组装的公式当成了字符值,而没有转换成可计算的表达式。当前代码里用quo(paste0(...))得到的是一个包含字符串生成逻辑的quosure,而不是我们需要的、能直接计算的表达式quosure,所以UQ()注入后只是输出字符串,不会执行计算。
正确的解决方案:将字符串转为表达式再注入
我们可以用rlang::parse_expr()把组装好的字符串公式转换成表达式,再用!!(bang-bang操作符)注入到case_when中。以下是修改后的函数:
library(dplyr) library(rlang) bus_inc <- function(tb, suffix) { # 1. 组装每个条件对应的字符串公式 bus1_str <- paste0("incbus", suffix, " + incfarm", suffix) bus2_str <- paste0("incbus2", suffix, " + incfarm", suffix) bus3_str <- paste0("incbus", suffix, " + incfarm2", suffix) # 2. 将字符串转为可计算的表达式 bus1_expr <- parse_expr(bus1_str) bus2_expr <- parse_expr(bus2_str) bus3_expr <- parse_expr(bus3_str) # 3. 在case_when中用!!注入表达式 tb %>% mutate(bus = case_when( year < 1968 ~ !!bus1_expr, year > 1967 & year < 1976 ~ !!bus2_expr, year > 1975 ~ !!bus3_expr )) }
测试验证
用你的测试数据运行:
incbus_99 <- 1:56 incfarm_99 <- 57:112 incbus2_99 <- incbus_99 + 0.5 incfarm2_99 <- incfarm_99 * 10 year <- 1962:2017 test_tb <- tibble(year, incbus_99, incfarm_99, incbus2_99, incfarm2_99) my_test <- bus_inc(tb = test_tb, suffix = "_99")
查看结果:
- 1962年的
bus值:1 + 57 = 58,符合预期 - 1968年的
bus值:(7 + 0.5) + (57 + 6) = 70.5,完全匹配你的期望
为什么之前的方法无效?
你之前用quo(paste0(...))得到的是一个quosure,它包裹的是paste0函数的调用逻辑,而不是最终的字符串转成的表达式。当你用UQ()注入时,实际上是执行了paste0生成字符串,但case_when会把这个字符串当成字符值,而不是要计算的算术表达式,所以结果会是类似"incbus_99 + incfarm_99"的文本,而非数值。
关于parse的安全性
你提到Martin Maechler反对用parse(text=...),他的顾虑主要是不可信来源的字符串可能带来代码注入风险。但在你的场景中,字符串是你自己通过后缀组装生成的,完全可控,所以使用parse_expr()(本质和parse(text=)类似,但更贴合tidyverse的编程风格)是安全的。
更紧凑的写法
如果你不想提前定义变量,也可以直接在case_when里内联转换:
bus_inc <- function(tb, suffix) { tb %>% mutate(bus = case_when( year < 1968 ~ !!parse_expr(paste0("incbus", suffix, " + incfarm", suffix)), year > 1967 & year < 1976 ~ !!parse_expr(paste0("incbus2", suffix, " + incfarm", suffix)), year > 1975 ~ !!parse_expr(paste0("incbus", suffix, " + incfarm2", suffix)) )) }
内容的提问来源于stack exchange,提问作者andrewH
相关产品推荐
相关产品推荐

