通过匹配多列中的多个字符串来过滤数据框

时间:2017-07-10 01:28:44

标签: r grep dplyr subset

我尝试使用dplyrgrep库使用我的数据框的多列中的字符串列表来过滤我的数据框失败了。我认为这是一项简单的任务,但要么没有人问过我的具体问题,要么就像我原先认为的那样容易。

对于以下数据框...

foo <- data.frame(var.1 = c('a', 'b',' c'),
           var.2 = c('b', 'd', 'e'),
           var.3 = c('c', 'f', 'g'),
           var.4 = c('z', 'a', 'b'))

...我希望能够逐行过滤以查找包含所有三个变量a,b和c的行。 我追捧的答案只会返回第1行,因为它包含a,b和c,并且不会返回第2行和第3行,即使它们包含三个搜索变量中的两个,它们也不包含所有三个同一行。

我遇到的问题是,grep只允许一次指定向量或一列,而我真的只关心在同一行中的多个列中查找字符串。

我还使用dplyr filter使用%in%,但它只会在存在任何变量时返回:

foo %>% 
  filter(var.1 %in% c('a', 'b', 'c') |
           var.2 %in% c('a', 'b', 'c') |
           var.3 %in% c('a', 'b', 'c'))

感谢您提供的所有帮助,如果您需要任何澄清,请与我们联系!

2 个答案:

答案 0 :(得分:4)

这是基础R中的一种方法,我们检查foo的元素是否等于"a""b""c",添加布尔值并检查是否每行的布尔值之和大于或等于3

Reduce("+", lapply(c("a", "b", "c"), function(x) rowSums(foo == x) > 0)) >=3
#[1]  TRUE FALSE FALSE

<强>计时

foo = matrix(sample(letters[1:26], 1e7, replace = TRUE), ncol = 5)
system.time(Reduce("+", lapply(letters[1:20], function(x) rowSums(foo == x) > 0)) >=20)
#   user  system elapsed 
#   3.26    0.48    3.79 

system.time(apply(foo, 1, function(x) all(letters[1:20] %in% x)))
#   user  system elapsed 
#  18.86    0.00   19.19 


identical(Reduce("+", lapply(letters[1:20], function(x) rowSums(foo == x) > 0)) >=20, 
          apply(foo, 1, function(x) all(letters[1:20] %in% x)))
#[1] TRUE
> 

答案 1 :(得分:2)

您的问题源于尝试将“tidyverse”解决方案应用于不整洁的数据。这是一个整洁的解决方案,它使用melt来使您的数据整洁。看看这个解决方案有多整洁?

> library(reshape2)
> rows = foo %>%
      mutate(id=1:nrow(foo)) %>% 
      melt(id="id") %>% 
      filter(value=="a" | value=="b" | value=="c") %>%
      group_by(id) %>% 
      summarize(N=n()) %>% 
      filter(N==3) %>%
      select(id) %>%
      unlist
Warning message:
attributes are not identical across measure variables; they will be dropped 

它为您提供了匹配行索引的向量,然后您可以使用以下内容对原始数据框进行子集化。

> foo[rows,]
  var.1 var.2 var.3 var.4
1     a     b     c     z
>