根据组属性过滤组中的行

时间:2019-07-02 22:01:14

标签: r dplyr

假设我有一个带有分组变量和逻辑变量的小标题,该变量指示行是否是该组的主要响应。

我要执行以下操作:

  1. 如果group中的任何行被标记为is_primary,则保留该行,但不保留该行中的其他行
  2. 如果group中没有用is_primary标记的行,请全部保留
  3. 根据上述内容过滤行

以下是一些示例数据:

library(tidyverse)
data <- tibble(group=c("A","A","A","B","B","C","C","C","C"),
               is_primary=c(FALSE, FALSE, FALSE,FALSE,TRUE,FALSE,FALSE,TRUE,TRUE),
               value=c(1,2,3,4,5,6,7,8,9))

在上面的示例中,我想保留所有A行,因为没有is_primary==TRUE行,仅保留第二行B,并保留最后一行两行C

我认为显而易见的解决方案是:

data %>%
  group_by(group) %>%
  mutate(keep_row=ifelse(any(is_primary),is_primary,TRUE))

但这会导致以下结果,但不满足上述条件。

# A tibble: 9 x 4
# Groups:   group [3]
  group is_primary value keep_row
  <chr> <lgl>      <dbl> <lgl>   
1 A     FALSE          1 TRUE    
2 A     FALSE          2 TRUE    
3 A     FALSE          3 TRUE    
4 B     FALSE          4 FALSE   
5 B     TRUE           5 FALSE   
6 C     FALSE          6 FALSE   
7 C     FALSE          7 FALSE   
8 C     TRUE           8 FALSE   
9 C     TRUE           9 FALSE 

但是,如果我创建一个中间变量来指示该组是否具有主键,那么它会起作用。

data %>%
  group_by(group) %>%
  mutate(has_primary=ifelse(any(is_primary),TRUE,FALSE)) %>%
  mutate(keep_row=ifelse(has_primary,is_primary,TRUE))

这导致keep_row是正确的:

# A tibble: 9 x 5
# Groups:   group [3]
  group is_primary value has_primary keep_row
  <chr> <lgl>      <dbl> <lgl>       <lgl>   
1 A     FALSE          1 FALSE       TRUE    
2 A     FALSE          2 FALSE       TRUE    
3 A     FALSE          3 FALSE       TRUE    
4 B     FALSE          4 TRUE        FALSE   
5 B     TRUE           5 TRUE        TRUE    
6 C     FALSE          6 TRUE        FALSE   
7 C     FALSE          7 TRUE        FALSE   
8 C     TRUE           8 TRUE        TRUE    
9 C     TRUE           9 TRUE        TRUE

ifelse中发生什么情况,第一个解决方案不起作用?

2 个答案:

答案 0 :(得分:3)

当'is_primary'中没有TRUE元素时,我们可以使用if/else条件返回行,或者else仅返回'is_primary'为TRUE的行

library(dplyr)
data %>%
    group_by(group) %>%
    filter(if(!any(is_primary)) TRUE else is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

也可以在|条件下完成

data %>%
   group_by(group) %>%
   filter(!any(is_primary) | is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

或者另一个选择是

data %>%
  group_by(group) %>%
  filter(sum(is_primary) == 0 | is_primary)
# A tibble: 6 x 3
# Groups:   group [3]
#  group is_primary value
#  <chr> <lgl>      <dbl>
#1 A     FALSE          1
#2 A     FALSE          2
#3 A     FALSE          3
#4 B     TRUE           5
#5 C     TRUE           8
#6 C     TRUE           9

或使用slice

data %>% 
  group_by(group) %>% 
  slice(if(!any(is_primary)) row_number() else which(is_primary))

上面的data.table选项是

library(data.table)
setDT(data)[data[, .I[!any(is_primary)|is_primary], by = group]$V1]

或使用base R

data[with(data, !ave(is_primary, group, FUN = any) | is_primary),]

ifelse的问题在于,根据?ifelse

  

ifelse(测试,是,否)

     

如果是或否太短,则将其元素回收。当且仅当测试的任何一个要素为真,并且类似地为否,才会评估是。

使用OP的代码

 ifelse(any(is_primary),TRUE,FALSE)

any返回一个逻辑向量length1。根据?any

  

该值是长度为1的逻辑向量。

根据上面的ifelse文档,这些值被回收

答案 1 :(得分:3)

您的问题是ifelse()返回的向量就是输入的长度。当您传递ifelse(any(),...)时,该any()仅返回对该组重复的单个向量。您可以通过

看到
x <- c(F,T,F,T, F)
ifelse(any(x), x, TRUE)
# [1] FALSE

注意仅返回一个值。 ifelse()不仅仅是适当的if \ else语句的快捷方式。它是向量化函数,因此在尝试以非向量化方式有条件地执行代码时,请小心不要使用它。

表达过滤器的另一种方法是

data %>% 
  group_by(group) %>% 
  filter(any(is_primary) & is_primary | !any(is_primary))