dplyr:使用不同的条件过滤每个列

时间:2018-02-18 22:40:09

标签: r dplyr

我正在使用dplyr过滤数据框。问题是列之间的谓词不同。 请在下面找到一个包含三列和三个谓词的最小示例:

library(tidyverse)

set.seed(123)
dframe <- rerun(3, rnorm(5)) %>%
  set_names(paste0("var", 1:3)) %>% 
  data.frame

cond <- c(2, 1, -1.4)
dframe %>% filter(var1 < cond[1] & var2 < cond[2] & var3 > cond[3])

有没有办法过滤数据集而不明确说明filter中的谓词?

编辑:问题的一个潜在解决方案显然是使用for循环,请参阅下面的代码。但是,可能会有更优雅的解决方案。

dframe_help <- dframe
cond <- c(2, 1, -1.4)
isSmaller <- c(TRUE, TRUE, FALSE)
for(i in seq_along(cond)) {
  if (isSmaller[i])
    dframe_help <- dframe_help %>% filter_at(.vars = vars(num_range(prefix = "var", range = i)), 
                                             .vars_predicate = all_vars(. < cond[i]))
  else
    dframe_help <- dframe_help %>% filter_at(.vars = vars(num_range(prefix = "var", range = i)), 
                                            .vars_predicate = all_vars(. > cond[i]))
}

2 个答案:

答案 0 :(得分:2)

您需要某种对象来指定是使用<还是>。我创建了一个名为less的{​​{1}},1<>为0。

require(purrr); require(magrittr)
filter2 <- function(dframe, cond, less){
            rows <- pmap(list(cond, less, dframe), 
                         function(cond, less, x) if(less) x < cond else x > cond
                         ) %>% 
                        pmap_lgl(all)
            dframe[rows,]
}

dframe %>% filter2(cond = c(2, 1, -1.4), less = c(1, 1, 0))

或者,明确传递要用于每个变量的函数。

filter3 <- function(df, y, fun){
        df[pmap(list(df, y, fun), function(x, y, fun) fun(x, y)) %>% 
                pmap_lgl(all)
        ,]
}


dframe %>% filter3(y = c(2, 1, -1.4), fun = list(`<`, `<`, `>`))

答案 1 :(得分:0)

不确定您的意思是&#39;自动化&#39;这个过程,但这里有几个选项。

如果您希望以更加清晰的方式过滤多个要素,可以创建独立过滤功能:

cond <- c(2, 1, -1.4)
filter_using_conditions <- function(df) {
  df[df$var1 < cond[1] & df$var2 < cond[2] & df$var3 > cond[3],]
}
dframe %>%
  filter_using_conditions()
        var1       var2       var3
2  0.4978505 -0.2179749  0.8377870
3 -1.9666172 -1.0260044  0.1533731
4  0.7013559 -0.7288912 -1.1381369
5 -0.4727914 -0.6250393  1.2538149

如果要使用运算符和值的向量实现解决方案,可以尝试执行一些字符串操作,并使用base::eval()glue::eval()生成逻辑向量以对数据帧进行子集化。以下是使用purrr:mappurrr:map2的示例(它不是很优雅,但希望得到重点):

cond <- c(2, 1, -1.4)
operators <- c("<", "<", ">")

filter_conditions <- function(dframe, conds, operators) {
  x <- paste(operators, conds, sep = " ")
  rows_to_use <- map2(dframe, x, paste) %>%
    map(map_lgl, glue::evaluate, NULL) %>%
    as_tibble() %>%
    na_if(FALSE) %>%
    complete.cases()
  dframe[rows_to_use,]
}
filter_conditions(dframe, cond, operators)
        var1       var2       var3
2  0.4978505 -0.2179749  0.8377870
3 -1.9666172 -1.0260044  0.1533731
4  0.7013559 -0.7288912 -1.1381369
5 -0.4727914 -0.6250393  1.2538149   

此示例使用purrr:map2()使用指定的运算符条件对为每个数据点生成单个字符串,然后使用glue::evaluate()purrr:map2()将这些字符串作为命令执行并返回逻辑向量。使用dplyr::na_if()以便稍后使用complete.cases()来获取与行索引相对应的逻辑向量。

map2(dframe, x, paste)
$var1
[1] "1.78691313680308 < 2"   "0.497850478229239 < 2"  "-1.96661715662964 < 2"  "0.701355901563686 < 2" 
[5] "-0.472791407727934 < 2"

$var2
[1] "-1.06782370598685 < 1"  "-0.217974914658295 < 1" "-1.02600444830724 < 1"  "-0.72889122929114 < 1" 
[5] "-0.625039267849257 < 1"

$var3
[1] "-1.68669331074241 > -1.4" "0.837787044494525 > -1.4" "0.153373117836515 > -1.4"
[4] "-1.13813693701195 > -1.4" "1.25381492106993 > -1.4"