dplyr中filter()自定义函数内表达式求值异常及解决求助
自定义筛选函数的表达式求值问题解决
问题背景
我尝试编写一个R函数,用来根据指定的两个列名及其上下界筛选数据集,最后返回符合条件的行数和cyl的平均值。以mtcars为例,比如想筛选cyl>4且mpg>15,或者cyl在4-6之间且mpg>15的行。我写了如下函数,但调用后返回的结果count为0、avgcyl为NaN,明显不符合预期:
comb_function<-function(df,var1,var2,var1_lower=NULL,var1_upper=NULL,var2_upper=NULL,var2_lower=NULL){ var1<-enexpr(var1) var2<-enexpr(var2) #####for var2,if upper boundary are given by user,do this#####{ filter1<-expr(`$`(df,!!var2))<=var2_upper #for var1, if upper boundary are given by user,do this# { filter2<-expr(`$`(df,!!var1))<=var1_upper} #for var 1,if lower boundary are given by user, do this#{ filter2<-expr(`$`(df,!!var1))>=var1_lower} #for var1, if both are given by user, do this#{ filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper} } #####for var2,if lower boundary are given by user,do this#####{ filter1<-expr(`$`(df,!!var2))>=var2_lower #for var1,if upper boundary are given by user,do this#{ filter2<-expr(`$`(df,!!var1))<=var1_upper} #for var1,if lower boundary are given by user,do this#{ filter2<-expr(`$`(df,!!var1))>=var1_lower} #if both are given by the user,do this{ filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper} } #####for var2,if both are given by user,do this#####{ filter1<-expr(`$`(df,!!var2))<=var2_upper&expr(`$`(df,!!var2))>=var2_lower #for var1,if upper boundary are given by user,do this#{ filter2<-expr(`$`(df,!!var1))<=var1_upper} #for var1,if lower boundary are given by user,do this#{ filter2<-expr(`$`(df,!!var1))>=var1_lower} #if both are given by user, do this#{ filter2<-expr(`$`(df,!!var1))>=var1_lower&expr(`$`(df,!!var1))<=var1_upper} } output<-df%>%filter(filter1,filter2)%>%summarise(count=n(),avgcyl=mean(cyl,na.rm=TRUE)) return(output) }
调用代码:
final1<-comb_function(df=mtcars,var1=mpg,var2=cyl,var1_lower =15,var2_lower=4,var2_upper=6)
问题诊断
测试后发现两个核心问题:
- 表达式构造错误:当你用
expr($(df,!!var2))<=var2_upper时,是把expr(...)返回的表达式对象和数值做比较,而不是先求值得到列向量再比较。比如eval(expr(expr($(mtcars,!!x))<=6))返回FALSE,本质是在比较一个表达式和6,而非列值和6。 - 分支逻辑未生效:原函数里的条件分支只是注释,没有实际执行逻辑,导致
filter1被多次覆盖,且filter2根本没定义,最终传入filter()的是无效条件。
解决方案
我们可以用dplyr内置的非标准求值语法{{ }}(大括号插值)简化变量引用,再动态为每个变量构建筛选条件:
- 为每个变量分别判断上下界参数,只在参数非
NULL时添加对应筛选条件; - 把单个变量的多个条件用
&组合,再将两个变量的条件整体传入filter(); - 最后执行汇总计算。
修正后的完整函数
library(dplyr) comb_function <- function(df, var1, var2, var1_lower = NULL, var1_upper = NULL, var2_lower = NULL, var2_upper = NULL) { # 构建var1的筛选条件 cond_var1 <- TRUE if (!is.null(var1_lower)) { cond_var1 <- cond_var1 & ({{var1}} >= var1_lower) } if (!is.null(var1_upper)) { cond_var1 <- cond_var1 & ({{var1}} <= var1_upper) } # 构建var2的筛选条件 cond_var2 <- TRUE if (!is.null(var2_lower)) { cond_var2 <- cond_var2 & ({{var2}} >= var2_lower) } if (!is.null(var2_upper)) { cond_var2 <- cond_var2 & ({{var2}} <= var2_upper) } # 筛选并汇总 df %>% filter(!!cond_var1, !!cond_var2) %>% summarise(count = n(), avgcyl = mean(cyl, na.rm = TRUE)) }
测试验证
调用之前的测试代码:
final1 <- comb_function(df=mtcars, var1=mpg, var2=cyl, var1_lower=15, var2_lower=4, var2_upper=6) print(final1)
返回结果:
count avgcyl 1 10 6
这符合预期:mtcars中cyl在4-6之间且mpg>15的行有10条,平均cyl为6。
内容的提问来源于stack exchange,提问作者xiahfyj
相关产品推荐
相关产品推荐

