передайте несколько столбцов для работы в dplyr

#r #dplyr

#r #dplyr

Вопрос:

Я пытаюсь разобраться в синтаксисе работы с dplyr, и у меня возникают проблемы с тем, как передать более одного столбца другой функции (например, str_detect). Я хочу выполнить поиск по tibble и выбрать все строки, в которых обнаружена определенная строка. Я могу запустить это для определенного столбца (например, col3 в примере ниже), но хотел бы просмотреть некоторые и / или все столбцы.

 library(dplyr)
library(stringr)

col1 <- c("plate_ABC", "text", "text", "text")
col2 <- c("text", "this is plate B", "text", "text")
col3 <- c("text", "text", "C-plate", "text")

df <- as_tibble(data_frame(col1, col2, col3))

df %>% filter(str_detect(col3, "plate"))
 

Вывод:

 df %>% filter(str_detect(col3, "plate"))

## A tibble: 1 x 3
#  col1  col2  col3   
#  <chr> <chr> <chr>  
#1 text  text  C-plate
 

Желаемый результат:

 df %>% filter(str_detect(?SOME/ALL Cols?, "plate"))

## A tibble: 3 x 3
#  col1      col2            col3   
#  <chr>     <chr>           <chr>  
#1 plate_ABC text            text   
#2 text      this is plate B text   
#3 text      text            C-plate
 

Комментарии:

1. Ваше ожидаемое решение не должно использовать filter или reduce ? Я в замешательстве

2. Нет, извините, если я неясен. Любые и все решения великолепны. Я очень новичок в программировании (всего пару недель), поэтому мне требуется некоторое время, чтобы понять синтаксис и определить наиболее удобный способ использования различных команд. Я думаю, что в этом случае rowwise %>% filter наиболее интуитивно понятен для моего понимания.

3. Он rowwise должен быть медленным по сравнению с векторизованным вариантом

4. Спасибо, что обратили на это внимание. Я думаю, что сейчас скорость не является проблемой (таблицы данных очень малы), но я буду иметь это в виду по мере того, как буду лучше знакомиться с кодированием.

Ответ №1:

Вы можете использовать across :

 library(dplyr)
library(stringr)

df %>% filter(Reduce(`|`, across(.fns = ~str_detect(., "plate"))))

#  col1      col2            col3   
#  <chr>     <chr>           <chr>  
#1 plate_ABC text            text   
#2 text      this is plate B text   
#3 text      text            C-plate
 

Или по строкам :

 df %>%
  rowwise() %>%
  filter(any(str_detect(c_across(), 'plate')))
 

Если у вас более старая версия dplyr (<1.0.0), вы можете использовать filter_all / filter_at :

 df %>% filter_all(any_vars(str_detect(., 'plate')))
 

Комментарии:

1. Спасибо @Ronak. Итак, я думаю, что понимаю первое: str_detect будет оценивать по всем столбцам, пытаясь найти табличку. Если какая-либо из этих оценок равна T, строка будет отфильтрована. И rowwise означает, что каждая строка оценивается индивидуально. Это также должно быть полезно при генерации сводной статистики по столбцам. Я борюсь со вторым решением, так как мне трудно понять синтаксис и что именно указывают точка, тильда, …. Я постараюсь разобраться в этом по ходу дела.

2. тильда( ~ ) используется для применения синтаксиса в стиле формулы. Это похоже на анонимную функцию.

Ответ №2:

Мы можем использовать base R для этого

 df[Reduce(`|`, lapply(df, grepl, pattern = 'plate')),]
 

-вывод

 # A tibble: 3 x 3
#  col1      col2            col3   
#  <chr>     <chr>           <chr>  
#1 plate_ABC text            text   
#2 text      this is plate B text   
#3 text      text            C-plate
 

Или с помощью rowSums

 df[rowSums(`dim<-`(grepl('plate', as.matrix(df)), dim(df))) > 0,]
 

Или с помощью tidyverse

 library(dplyr)
library(purrr)
library(stringr)
df %>%
   filter(across(everything(), ~ str_detect(., 'plate')) %>% 
           reduce(`|`))
# A tibble: 3 x 3
#  col1      col2            col3   
#  <chr>     <chr>           <chr>  
#1 plate_ABC text            text   
#2 text      this is plate B text   
#3 text      text            C-plate
 

Тесты

Тайминги для немного большего набора данных

 df1 <- df[rep(seq_len(nrow(df)), 1e6), ]
system.time(df1 %>% filter(Reduce(`|`, across(.fns = ~str_detect(., "plate")))))
#   user  system elapsed 
#  1.597   0.139   1.736 

system.time(df1 %>%
  rowwise() %>%
  filter(any(str_detect(c_across(), 'plate'))))
 #  user  system elapsed 
 #178.694   1.477 180.864 

 system.time(df1 %>% filter_all(any_vars(str_detect(., 'plate'))) )
 # user  system elapsed 
 # 1.461   0.061   1.499 

 system.time(df1[Reduce(`|`, lapply(df1, grepl, pattern = 'plate')),])
 #   user  system elapsed 
 #  2.792   0.025   2.778 

 system.time(df1 %>%
   filter(across(everything(), ~ str_detect(., 'plate')) %>% 
        reduce(`|`)))
#   user  system elapsed 
#  1.471   0.054   1.505 
 

Комментарии:

1. Спасибо @akrun. Для решения «tidyverse» применяется тот же комментарий, что и выше (хотя его уже легче понять, используя reduce как отдельную команду, а не вкладывая ее в filter. Есть чему поучиться!