Перевод filter_all (any_vars ()) в filter (через ()) - PullRequest
3 голосов
/ 14 июля 2020

При обновлении моего собственного ответа в другой ветке я не смог придумать хорошее решение для замены последнего примера (см. Ниже). Идея состоит в том, чтобы получить все строки, в которых любой столбец содержит определенную строку, в моем примере «V».

library(tidyverse)

#get all rows where any column contains 'V'
diamonds %>%
  filter_all(any_vars(grepl('V',.))) %>%
  head
#> # A tibble: 6 x 10
#>   carat cut       color clarity depth table price     x     y     z
#>   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
#> 2 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
#> 3 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
#> 4 0.24  Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
#> 5 0.26  Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
#> 6 0.22  Fair      E     VS2      65.1    61   337  3.87  3.78  2.49


# this does naturally not give the desired output! 
diamonds %>%
  filter(across(everything(), ~ grepl('V', .))) %>%
  head
#> # A tibble: 0 x 10

Я нашел цепочку, в которой плакат размышляет над похожими прочее , но применение аналогичного logi c на grepl не работает.

### don't run, this is ugly and does not work
diamonds %>%
  rowwise %>%
  filter(any(grepl("V", across(everything())))) %>%
  head

Ответы [ 3 ]

3 голосов
/ 14 июля 2020

Это очень сложно, потому что пример показывает, что вы хотите отфильтровать данные из всех столбцов, когда любой из них удовлетворяет условию (т.е. вы хотите, чтобы объединение ). Это делается с помощью filter_all() и any_vars().

В то время как filter(across(everything(), ...)) отфильтровывает все столбцы, когда все из них соответствуют условию (т. Е. Это пересечение , что совершенно противоположно предыдущему).

Чтобы преобразовать его из пересечения в объединение (т.е. чтобы снова получить строки, где любые столбцов соответствуют условию), вам, вероятно, нужно проверить сумму строки для этого:

diamonds %>%
   filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)

Он суммирует все TRUE, которые появляются в строке, т.е. если есть хотя бы одно значение, удовлетворяющее условию, эта сумма строки будет > 0 и будет отображаться.

Извините, что across() не самый первый дочерний элемент filter(), но он хоть какое-то представление, как это сделать. : -)

Оценка:

Используя метод @ TimTeaFan, чтобы проверить, что:

 identical(
     {diamonds %>%
         filter_all(any_vars(grepl('V',.)))
     }, 
     {diamonds %>%
         filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)
     }
 )
 #> [1] TRUE

Тест:

Согласно наше обсуждение в ответе TimTeaFan, вот сравнение, на удивление, все решения имеют одинаковое время:

library(tidyverse)
microbenchmark::microbenchmark(
  filter_all = {diamonds %>%
      filter_all(any_vars(grepl('V',.)))}, 
  purrr_reduce = {diamonds %>%
      filter(across(everything(), ~ grepl('V', .)) %>% purrr::reduce(`|`))},
  base_reduce = {diamonds %>%
      filter(across(everything(), ~ grepl('V', .)) %>% Reduce(`|`, .))},
  rowsums = {diamonds %>%
      filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)},
  times = 100L,
  check = "identical"
)
#> Unit: milliseconds
#>          expr      min       lq     mean   median       uq      max neval
#>    filter_all 295.7235 302.1311 309.6455 305.0491 310.0335 449.3619   100
#>  purrr_reduce 297.8220 302.4411 310.2829 306.2929 312.2278 461.0194   100
#>   base_reduce 298.5033 303.6170 309.4147 306.1839 312.3518 409.5273   100
#>       rowsums 295.3863 301.0281 307.8517 305.3142 309.4793 372.8867   100

Создано 14.07.2020 пакетом реплекс ( v0.3.0)

1 голос
/ 14 июля 2020

Некоторые столбцы были ordered, и это повлияет на c_across. Вместо этого, если мы конвертируем в класс character, а затем выполняем grepl, он должен работать

library(dplyr)
library(ggplot2)
diamonds %>%
    head %>% 
    mutate(across(where(is.factor), as.character)) %>% 
    rowwise %>% 
    filter(any(grepl("V", c_across(where(is.character)))))
# A tibble: 3 x 10
# Rowwise: 
#  carat cut       color clarity depth table price     x     y     z
#  <dbl> <chr>     <chr> <chr>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
#1 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
#2 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
#3 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
1 голос
/ 14 июля 2020

Это эквивалент filter_all звонка, который вы отправили. Однако @akrun совершенно правильно указывает, что сначала он должен быть преобразован в символ. Тем не менее, это также верно для вашего оператора filter_all.

Идея состоит в том, чтобы использовать across(everything(), ~ grepl('V', .)) для преобразования всего data.frame в столбцы TRUE и FALSE относительно grepl('V', .). Однако для filter нужен вектор или data.frame с одним столбцом, поэтому мы преобразуем его с помощью reduce (|). Он объединяет первые два столбца с |, затем результат этого вызова с третьим столбцом и так далее, пока исходный data.frame не будет иметь один столбец с TRUE и FALSE, который затем можно использовать для фильтрации строк .

library(ggplot2)
library(dplyr)

diamonds %>%
  filter(across(everything(), ~ grepl('V', .)) %>% purrr::reduce(`|`)) %>% 
  head
#> # A tibble: 6 x 10
#>   carat cut       color clarity depth table price     x     y     z
#>   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
#> 1 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
#> 2 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
#> 3 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
#> 4 0.24  Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
#> 5 0.26  Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
#> 6 0.22  Fair      E     VS2      65.1    61   337  3.87  3.78  2.49

identical({diamonds %>%
            filter_all(any_vars(grepl('V',.)))}, 
          {diamonds %>%
            filter(across(everything(), ~ grepl('V', .)) %>% purrr::reduce(`|`))
            })
#> [1] TRUE

Создано 14.07.2020 с помощью пакета . (v0.3.0)

...