如何向dplyr::filter传递多组过滤参数?有无更简洁方案?
动态定义dplyr过滤条件的简洁方案
问题背景
我希望在不将列名和过滤值硬编码到dplyr::filter调用内部的情况下,对数据框的多列值进行过滤。想避免编写如下硬编码的代码:
df_in <- data.frame( a = c("first", "first", "first", "first", "last", "last", "last", "last"), b = c("second", "second", "loser", "loser", "second", "second", "loser", "loser"), c = 1:8 ) df_in df_out <- df_in %>% dplyr::filter( !grepl("a", a), b == "second", c < 5 ## 不想把这些条件写在这里 ) df_out
期望实现类似以下逻辑,将条件提前定义后再传入filter:
filt_crit <- prep_function(!grepl("a", a), b == "second", c < 5) df_out <- df_in %>% dplyr::filter(eval_function(filt_crit)) df_out
已经实现了单条件过滤:
filt_crit1 <- rlang::expr(!grepl("a", a)) df_partial <- df_in %>% dplyr::filter(eval(filt_crit1)) df_partial
也通过purrr::reduce实现了多条件过滤,但觉得这种方式繁琐:
filt_crit <- c(rlang::expr(!grepl("a", a)), rlang::expr(b == "second"), rlang::expr(c < 5)) df_out <- filt_crit %>% purrr::reduce(\(acc, nxt) dplyr::filter(acc, eval(nxt)), .init = df_in) df_out
想问:purrr::reduce是否是最直接的解决方案?
更简洁的实现方案
purrr::reduce并非最直接的方式,用rlang::exprs打包多条件,再结合!!!(解引用拼接)语法直接传入dplyr::filter,是更紧凑的方案:
方法1:直接打包+展开条件
library(dplyr) library(rlang) # 提前定义所有过滤条件 filt_crit <- exprs( !grepl("a", a), b == "second", c < 5 ) # 在filter中用!!!展开条件,效果和硬编码完全一致 df_out <- df_in %>% filter(!!!filt_crit) df_out
方法2:封装为自定义函数(贴合你期望的prep/eval逻辑)
如果想要更贴近你虚构的prep_function和eval_function形式,可以封装两个简单函数:
# 自定义prep函数:打包过滤条件 prep_filter <- function(...) { rlang::exprs(...) } # 自定义eval函数:在数据框上应用过滤条件 eval_filter <- function(data, conditions) { data %>% dplyr::filter(!!!conditions) } # 使用示例 filt_crit <- prep_filter(!grepl("a", a), b == "second", c < 5) df_out <- eval_filter(df_in, filt_crit) df_out
原理说明
rlang::exprs会将传入的多个表达式打包成列表,完整保留每个过滤条件的原始逻辑;!!!是rlang的解引用拼接操作符,它会把列表中的每个表达式直接插入到filter的参数位置,等价于直接在filter中写所有条件。
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

