根据不同的行条件删除组行

Removing rows of groups based on different row conditions

这就是我的数据框的样子。

dt <- read.table(text='

                         Name      ActivityType   GrpID                 
                         John       Sale           1            
                         John       Sale           2              
                         John       Webinar        3           
                         Kyle       Email          1       
                         Kyle       Seminar        2            
                         Kyle       Sale           3          
                         Kyle       Webinar        4
                         Kyle       Sale           5        
                         Tom        Email          1             
                         Tom        Video          2            
                         Tom        Seminar        3           
                                             ', header=T, row.names = NULL)

我想做 3 件事。

  1. 删除组(名称是组)的第一个 ActivityType 是 "Sale" 的行。这将删除 Name = John
  2. 的行
  3. 删除没有 ActivityType = Sale 的行。这会删除 Name = Tom
  4. 的行
  5. Return 剩余的组,其中第一个 ActivityType 不是 "Sale" 但在后面的行中有一个 ActivityType = "Sale"(如 1 & 2) 并且仅显示 ActivityType = Sale 的第一个实例之前的结果。所以它应该显示

                Name      ActivityType   GrpID
                 Kyle       Email          1       
                 Kyle       Seminar        2            
                 Kyle       Sale           3 
    

    不必像描述的那样是 3 个步骤。我只需要最终输出。我正在考虑在 data.table 中使用 SD 功能,但不知道如何添加这些条件。非常感谢您的帮助。

data.table中:

setDT(dt)

sl <- "Sale" #since we re-use it so much...
#1)
dt[ , if (!ActivityType[1L] == sl) .SD, by = Name]

#2)
dt[ , if (any(ActivityType == sl)) .SD, by = Name]

#3)
dt[ , {x <- ActivityType == sl; if(!x[1] & any(x)) .SD[1:which.max(x)]}, by = Name]

(请注意,第三种情况包含前两种情况,所以我假设您需要三种不同的输出...否则只坚持最后一种)

使用 dplyr 这将适用于您上面的示例

dt %>%
    group_by(Name) %>%
    filter( sum((GrpID == 1 & ActivityType=='Sale')) == 0 ) %>%
    filter( sum(ActivityType=='Sale') > 0 ) %>%
    filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>%
    ungroup

#Source: local data frame [3 x 3]
#
#    Name ActivityType GrpID
#  (fctr)       (fctr) (int)
#1   Kyle        Email     1
#2   Kyle      Seminar     2
#3   Kyle         Sale     3

虽然可能有更简洁的方法来执行此操作。

编辑: 我添加了输出和 ungroup 以删除分组。

编辑 2: 根据 MichaelChirico

的建议
dt %>%
    group_by(Name) %>%
    filter( !any(ActivityType == 'Sale' & GrpID == 1) )  %>% # 1
    filter(  any(ActivityType == 'Sale') )               %>% # 2
    filter( GrpID <= min(GrpID[ActivityType == 'Sale'])) %>% # 3
    ungroup

上述解决方案使用 any 而不是 sum%>% 是管道运算符)。但这并不是说不能提高效率。如果有人建议更有效和/或更具可读性的解决方案,我将很乐意再次更新此 dplyr 解决方案。

编辑 3

下面是第 3 项的替代解决方案,该解决方案基于@MichaelChirico 的comment/solution。这将所有 3 个条件组合在一个过滤语句中(不使用上面的渐进式过滤)。

dt %>%
    group_by(Name) %>%
    mutate(x = (ActivityType == 'Sale') ) %>%
    filter( !x[1],
            any(x),
            row_number() <= which.max(x))  %>%
    ungroup %>%
    select(-x)
# For those who prefer to roll their own
result.list <- by(dt, dt$Name, FUN = function(x) {
  f <- match("Sale", x$ActivityType)
  if(!is.na(f) & (f != 1) ) return(head(x, f))
})

result.df <- do.call(rbind, result.list)