使用dplyr进行分组并筛选

5

首先,请告诉我是否我的dplyr使用不佳,因为我不确定我是否以最佳方式处理这个问题。我有下面的数据框:

mydf = data.frame(user = c(7,7,7,7,7,7,7,8,8,8,8,8,8),
                  col1 = c('0','0','1','1','0','3','NULL','3','3','0','1','0','0'),
                  col2 = runif(n=13),
                  col3 = letters[1:13], 
                  stringsAsFactors = FALSE)

> mydf
   user col1      col2 col3
1     7    0 0.7607907    a
2     7    0 0.1580448    b
3     7    1 0.8063540    c
4     7    1 0.7331512    d
5     7    0 0.2433631    e
6     7    3 0.2357065    f
7     7 NULL 0.4864172    g
8     8    3 0.6806089    h
9     8    3 0.2229874    i
10    8    0 0.6187911    j
11    8    1 0.7617177    k
12    8    0 0.5884821    l
13    8    0 0.4985750    m

我想要做的过滤有些冗长,但我会尝试 - 我想通过删除所有 col1 == '0' 为行来过滤数据框,如果该行出现在该用户的第一个 col1 == '1' 之后。(粗体表示我弄错了原问题,颠倒了0和1)。
例如,对于用户7,第3行具有 col1 == '1',因此我想过滤所有在第3行之后且 col1 == '0' 的行(在本例中仅为第5行)。然后,对于用户8,第11行是该用户的第一行,其中 col1 == '1',因此我想过滤行12和13,因为 col1 == '0'。
我的最终输出应该类似于这样:
> mydf
   user col1      col2 col3
1     7    0 0.7607907    a
2     7    0 0.1580448    b
3     7    1 0.8063540    c
4     7    1 0.7331512    d
6     7    3 0.2357065    f
7     7 NULL 0.4864172    g
8     8    3 0.6806089    h
9     8    3 0.2229874    i
10    8    0 0.6187911    j
11    8    1 0.7617177    k

我尝试了以下方法,但它没有奏效。我认为添加一个rownums列,然后按用户分组,再按照我描述的方式进行筛选会起作用。我的想法是我的filter调用存在问题:
mydf %>%
  mutate(rownums = 1:nrow(mydf)) %>%
  group_by(user) %>%
  filter(!(col1 == "0" & rownums > min(which(col1 == "1"))))

# A tibble: 9 x 5
# Groups:   col0 [2]
   user  col1      col2  col3 rownums
  <dbl> <chr>     <dbl> <chr>   <int>
1     7     0 0.2088034     a       1
2     7     0 0.2081894     b       2
3     7     1 0.1825428     c       3
4     7     1 0.2143353     d       4
5     7     3 0.1979774     f       6
6     7  NULL 0.2990799     g       7
7     8     3 0.7808038     h       8
8     8     3 0.1694272     i       9
9     8     1 0.1526450     k      11

这个输出和正确的输出的区别在于,这个输出错误地过滤了原始数据框的第10行。如有帮助,请指教!编辑 - 我特别想知道在使用dplyr时,group_by() %>% filter()是否是不好的做法。我的99% group_by()都跟着summarize(),这显然更有意义。编辑2 - 我想我搞定了!
mydf %>%
  group_by(col0) %>%
  mutate(rownums = 1:length(col0)) %>%
  filter(!(col1 == "0" & rownums > min(which(col1 == "1"))))

简单地反转mutate()和group_by()的调用顺序,并稍微调整mutate()的调用,看起来已经完成了。但我很乐意听取更好的方法。

4个回答

4

有一个 cumany 函数,它对于这些连续的条件非常有用,例如:

mydf %>%
  group_by(user) %>% 
  mutate(seen_one = cumany(col1 == "1")) %>%
  filter(!seen_one | col1 != "0")

这意味着在“1”已经在“流”中后,将所有行标记为seen_one,然后保留不满足条件的行。(使用filter的语义要求反转条件以“摆脱”行,!(A & B) == !A | !B。)


1
我认为这是一个很棒的想法,尽管输出结果与OP的期望有很大的不同。 - Aramis7d
OP对需求不是很一致,首先他说要删除col1 == 1的行,然后又删除了col1 == 0的第12和13行。 - liborm
就像我说的那样,筛选过程很啰嗦,而且仔细看了一下,我搞砸了。 - Canovice
好的,那么我的代码是否实现了你的意图?如果没有,请修复你那啰嗦的描述。 - liborm
已经修复了,但是我的期望输出仍然和发布的一样。 - Canovice
显示剩余2条评论

2

通过稍微更新您的尝试,可能会解决这个问题:

library(dplyr)
mydf %>%
   group_by(user) %>%
   filter(col1 != 0 | row_number() < which.max(col1 == 1))


#    user   col1        col2  col3
#   <dbl> <chr>       <dbl> <chr>
# 1     7     0 0.756522673     a
# 2     7     0 0.168314555     b
# 3     7     1 0.977254798     c
# 4     7     1 0.722721694     d
# 5     7     3 0.407849378     f
# 6     7  NULL 0.245335151     g
# 7     8     3 0.003423735     h
# 8     8     3 0.191716738     i
# 9     8     0 0.626846893     j
#10     8     1 0.546459621     k

使用filter函数,我们选择所有col1不等于0的行或者当前行小于该组中第一个出现1的索引位置的行。


1
这是一个通过 dplyr 的想法。
library(dplyr)

df %>% 
 group_by(user) %>% 
 mutate(id1 = row_number(), new_col = max(which(col1 == 1)+1)) %>% 
 filter(!(col1 == 0 & id1 >= new_col))

这表示:

# A tibble: 10 x 6
# Groups:   user [2]
    user  col1       col2  col3   id1 new_col
   <dbl> <chr>      <dbl> <chr> <int>   <dbl>
 1     7     0 0.54742608     a     1       5
 2     7     0 0.89271859     b     2       5
 3     7     1 0.48999057     c     3       5
 4     7     1 0.17163211     d     4       5
 5     7     3 0.96146770     f     6       5
 6     7  NULL 0.31368382     g     7       5
 7     8     3 0.82051455     h     1       5
 8     8     3 0.30705440     i     2       5
 9     8     0 0.18545358     j     3       5
10     8     1 0.04834678     k     4       5

1
我喜欢在mutate()中创建额外的列的想法,而不是使用过于复杂的filter()。mutate() %>% filter()使得filter更易于阅读。 - Canovice

1

另外,基于@liborm回答所提供的方向:

mydf %>%
  group_by(user) %>%
  mutate(k = cumany(col1 == '0'), j = cumany(col1 == '1')) %>%
  filter(!(col1 == 0 & k == TRUE & j == TRUE)) %>%
  select(-k,-j)

返回:
    user  col1  col2  col3
   <dbl> <chr> <dbl> <chr>
 1     7     0     1     a
 2     7     0     1     b
 3     7     1     0     c
 4     7     1     0     d
 5     7     3     0     f
 6     7  NULL     1     g
 7     8     3     0     h
 8     8     3     1     i
 9     8     0     1     j
10     8     1     0     k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接