如何使用 R 总结多个数字和基于文本的条件子集

How to use R summarise with multiple numeric and text-based conditional subsets

我有一个 table 每个 ID 包含两行。

table <- tibble(
  id = c(1,1,2,2,3,3,4,4,5,5),
  row1 = c(2,5,2,5,1,3,2,5,3,2),
  row2 = c("foo", "other foo", "bar", "bar", "bar", "bar other", "other", "foo", "other", "other")
)
> table
# A tibble: 10 × 3
      id  row1 row2 
   <dbl> <dbl> <chr>
 1     1     2 foo  
 2     1     5 other foo
 3     2     2 bar  
 4     2     5 bar  
 5     3     1 bar
 6     3     3 bar other
 7     4     2 other
 8     4     4 foo  
 9     5     3 other
10     5     2 other

我想根据连续三个规则将 table 解析为每个 ID 的一行:

  1. 如果对于每个ID,有一行row1大于等于5,则选择row1小于5的行。
  2. 否则,如果对于每个 ID,有一行 row2 包含单词 'other',请选择 row2 不包含单词 'other'
  3. 的行
  4. 否则,对于每个 ID,选择第一行。

我觉得一定有更直接的方法来做到这一点。到目前为止,这是我的尝试,但我不知道如何将 NA 解析为 return 'bar'.

table %>%
  group_by(id) %>%
  summarise(
    row1 = ifelse(max(row1) >= 5,
            first(row1[row1 < 5]),
            ifelse(
              grep("other", row2),
              ifelse(
                !is.na(first(row1[grep("other", row2, invert = T)])),
                first(row1[grep("other", row2, invert = T)]),
                first(row1)),
              first(row1))
    ),
    row2 = ifelse(
      max(row1) >= 5,
      first(row2[row1 < 5]),
      ifelse(
        grep("other", row2),
        ifelse(
          !is.na(first(row2[grep("other", row2, invert = T)])),
          first(row2[grep("other", row2, invert = T)]),
          first(row2)),
        first(row2)
    )
)
)

# A tibble: 5 × 3
     id  row1 row2 
  <dbl> <dbl> <chr>
1     1     2 foo  
2     2     2 NA   
3     3     1 bar  
4     4     2 foo  
5     5     3 other

期望的输出:

id row1 row2
1 2 foo
2 2 bar
3 1 bar
4 2 other
5 3 other

非常感谢您的帮助。

这是一个解决方案,利用这个小函数,f() 使用 tidyversedata.table

f <- function(r1,r2) {
  if(sum(r1>=5)==1) return(list("row1" =r1[r1<5], "row2"=r2[r1<5]))
  if(sum(grepl("other",r2))==1) return(list("row1" = r1[!grepl("other",r2)], "row2"=r2[!grepl("other",r2)]))
  list("row1"=r1[1],"row2"=r2[1])
}

用法

library(tidyverse)

table %>% 
  group_by(id) %>%
  summarize(n=list(f(row1,row2))) %>%
  unnest_wider(n)

library(data.table)

setDT(table)[, f(row1,row2), by=id]

输出:

      id  row1   row2
   <num> <num> <char>
1:     1     2    foo
2:     2     2    bar
3:     3     1    bar
4:     4     2  other
5:     5     3  other
table %>%
  group_by(id) %>%
  subset(
    case_when(
      any(row1 >= 5) ~ row1 < 5,
      any(grepl("other", row2)) ~ !grepl("other", row2),
      T ~ T
    )
  ) %>%
  filter(row_number() == 1) %>%
  ungroup()

这个答案利用了 dplyr 的分组能力来检查每个组中的 any() ,因此很容易知道组中是否发生了某种情况。

它还使用 case_when() 按优先顺序检查一系列条件,实现一系列 if/else。

最后,由于在任何情况下我们只想要符合条件的第一行,它使用函数 row_number() 来检查我们是否在组中的第一行,以便到 select 它。

输出为:

# A tibble: 5 x 3
     id  row1 row2     
  <dbl> <dbl> <chr>    
1     1     2 foo      
2     2     2 bar      
3     3     1 bar other
4     4     2 other    
5     5     3 other    
> 

一个dplyr解决方案:

table %>%
  group_by(id) %>%
  filter(row1 < 5 | n_distinct(row1 < 5) == 1) %>%
  filter(!grepl("other", row2) | n_distinct(grepl("other", row2)) == 1) %>%
  slice(1) %>% ungroup()

# # A tibble: 5 × 3
#      id  row1 row2 
#   <dbl> <dbl> <chr>
# 1     1     2 foo  
# 2     2     2 bar  
# 3     3     1 bar  
# 4     4     2 other
# 5     5     3 other

n_distinct(...) == 1用于判断一个条件是全TRUE还是全FALSE.

我们可以这样做:

library(dplyr)
library(tidyr)
library(stringr)

table %>%
  group_by(id) %>% 
  separate_rows(row2) %>%
  mutate(x = ifelse(row1>=5, min(row1),NA),
         y = ifelse(str_detect(row2, 'other'), !str_detect(row2, 'other'), NA)) %>% 
  slice(1) %>% 
  select(-c(x, y))
    id  row1 row2 
  <dbl> <dbl> <chr>
1     1     2 foo  
2     2     2 bar  
3     3     1 bar  
4     4     2 other
5     5     3 other