首页 > 解决方案 > dplyr 以编程方式过滤多个逻辑列的最佳实践

问题描述

要解决的问题

我需要两个函数来根据可能包含缺失值的列指示符(即逻辑)在小标题上实现和/或过滤。函数的参数应该是要考虑的列的字符向量。

我的解决方案

filter_checked <- function(db, vars = NULL) {
  db %>%
    dplyr::filter(
      dplyr::if_all(dplyr::all_of(vars), ~ !is.na(.x) & .x)
    )
}


filter_or_checked <- function(db, vars = NULL) {
  db %>%
    dplyr::filter(
      dplyr::if_any(dplyr::all_of(vars), ~ !is.na(.x) & .x)
    )
}

要通过的示例测试

test_that("filter checks", {
  foo <- tibble::tibble(
    id = 1:5,
    a = c(TRUE, TRUE, FALSE, FALSE, FALSE),
    b = c(NA, TRUE, NA, TRUE, NA)
  )


  expect_equal(filter_checked(foo)[["id"]], 1:5)
  expect_equal(filter_checked(foo, "a")[["id"]], 1:2)
  expect_equal(filter_checked(foo, "b")[["id"]], c(2, 4))
  expect_equal(filter_checked(foo, c("a", "b"))[["id"]], 2)

})



test_that("filter_or_checks", {
  foo <- tibble::tibble(
    id = 1:5,
    a = c(TRUE, TRUE, FALSE, FALSE, FALSE),
    b = c(NA, TRUE, NA, TRUE, NA)
  )


  expect_equal(filter_or_checked(foo)[["id"]], integer(0))
  expect_equal(filter_or_checked(foo, "a")[["id"]], 1:2)
  expect_equal(filter_or_checked(foo, "b")[["id"]], c(2, 4))
  expect_equal(filter_or_checked(foo, c("a", "b"))[["id"]], c(1, 2, 4))

})

我的问题

在我看来,我的功能太复杂了。无论如何,我认为这是我缺乏知识。那么,是否有更好的(即更容易阅读/理解/教授)tidyverse 解决方案来解决问题?

标签: rfilterdplyrtidyverse

解决方案


如果发现您的代码很有趣。

要回答,当您有许多布尔值(至少三个或更多)时,我有一个解决方案是将它们全部转换为一列,其中包含 0(FALSE)和 1(TRUE),例如对于五个布尔值,它看起来像:

00000
01000
11111
01001
11110

然后:

  • 要知道所有布尔值是否为 TRUE,您可以计算每个单元格中有多少个“1”,并要求有与列号一样多的“1”
  • 要知道至少一列是否为 TRUE,只需搜索字符串 '1'

就我而言,我没有考虑缺失值。但是你可以重新编码它们 2 例如。

最后,这涉及更多的数据准备和之后的不太复杂的功能(因为您不是在玩多个布尔值,而是只玩一个字符串)。

代码将类似于:

library(dplyr)

# Prepare data, from your data 
foo <- tibble::tibble(
  id = 1:5,
  a = c(TRUE, TRUE, FALSE, FALSE, FALSE),
  b = c(NA, TRUE, NA, TRUE, NA),
  d_bis = c(TRUE, TRUE, FALSE, FALSE, FALSE),
  e_bis = c(TRUE, TRUE, FALSE, FALSE, FALSE),
  f_bis = c(TRUE, TRUE, FALSE, FALSE, FALSE)
) %>% 
  mutate(a_bis = a, b_bis = b) %>% # copy columns to test
  mutate_at(vars(ends_with('_bis')), as.integer) %>% # convert logicals to integers
  mutate_at(vars(ends_with('_bis')), tidyr::replace_na, replace = 2) %>% # replace NA with 2
  mutate(af_bis = paste0(a_bis, b_bis, d_bis, e_bis, f_bis))
# A tibble: 5 x 9
     id a     b     d_bis e_bis f_bis a_bis b_bis af_bis
  <int> <lgl> <lgl> <dbl> <dbl> <dbl> <dbl> <dbl> <chr> 
1     1 TRUE  NA        1     1     1     1     2 12111 
2     2 TRUE  TRUE      1     1     1     1     1 11111 
3     3 FALSE NA        0     0     0     0     2 02000 
4     4 FALSE TRUE      0     0     0     0     1 01000 
5     5 FALSE NA        0     0     0     0     2 02000 

# list rows where at least one is TRUE
foo %>% 
  filter(grepl('1', af_bis))

# list rows where all columns are TRUE
foo %>% 
  filter(stringr::str_count(af_bis, '1') == 5L)

# list where at least one column is TRUE only if all columns are not missing
foo %>% 
  filter(grepl('1', af_bis) & ! grepl('2', af_bis))

推荐阅读