【问题标题】:pass on multiple columns to function within dplyr传递多列以在 dplyr 中运行
【发布时间】:2020-12-05 14:02:20
【问题描述】:

我正在尝试找出使用 dplyr 的语法,但遇到了如何将多个列传递给另一个函数(例如 str_detect)的问题。我想搜索一个小标题并选择检测到某个字符串的所有行。我可以针对特定列(例如下面示例中的 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

【问题讨论】:

  • 您的预期解决方案是否不应使用filterreduce?我很困惑
  • 不,如果我不清楚,抱歉。任何和所有的解决方案都很棒。我对编码非常陌生(只有几周的时间),所以我需要一段时间来理解语法并找出使用不同命令的最方便的方法。我认为在这种情况下,按行 %>% 过滤器对我来说是最直观的理解。
  • rowwise 应该比矢量化选项慢
  • 感谢您指出这一点。我认为现在速度不是问题(数据表非常小),但随着我对编码越来越熟悉,我会牢记这一点。

标签: r dplyr


【解决方案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 (filter_all/filter_at

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

【讨论】:

  • 谢谢@Ronak。所以我想我理解第一个:str_detect 将评估所有试图找到板块的列。如果这些评估中的任何一个为 T,则该行将被过滤。而逐行意味着每一行都被单独评估。这对于生成跨列的汇总统计信息也一定很有用。我正在为第二种解决方案苦苦挣扎,因为我很难理解语法以及句号、波浪号……表示的确切含义。我会试着弄清楚。
  • 波浪号(~) 用于应用公式样式语法。类似于匿名函数。
【解决方案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 

【讨论】:

  • 谢谢@akrun。对于 'tidyverse' 解决方案,与上述相同的注释适用(尽管使用 reduce 作为单独的命令而不是将其嵌套在过滤器中已经更容易理解了。要学习的东西很多!
猜你喜欢
  • 1970-01-01
  • 2020-07-05
  • 2022-01-16
  • 1970-01-01
  • 2017-02-24
  • 2022-10-06
  • 1970-01-01
  • 2018-05-08
  • 1970-01-01
相关资源
最近更新 更多