【问题标题】:Removing rows of groups based on different row conditions根据不同的行条件删除组的行
【发布时间】:2016-01-04 21:10:44
【问题描述】:

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

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)

我想做三件事。

  1. 删除组(名称是组)第一个 ActivityType 为“Sale”的行。这将删除 Name = John 的行
  2. 删除没有 ActivityType = Sale 的行。这将删除 Name = Tom
  3. 的行
  4. 返回剩余的组,其中第一个 ActivityType 不是“Sale”,但在后面的某些行中有一个 ActivityType =“Sale”(如 1 和 2 中所述),并且只显示第一个 ActivityType = Sale 实例的结果.所以它应该显示

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

    它不必是描述的 3 个步骤。我只需要最终输出。我正在考虑在 data.table 中使用 SD 函数,但不知道如何添加这些条件。我将真诚地感谢您的帮助。

【问题讨论】:

    标签: r data.table dplyr zoo


    【解决方案1】:

    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]
    

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

    【讨论】:

    • 非常感谢您的回答!
    • @gibbz00 本着data.table 效率的精神,请注意1:which.max(x) 有可能创建一个不必要的大向量;这个短语可以用{n &lt;- which.max(x); if (n &lt;= .N) 1:n else -(n + 1):.N} 替换以提高效率(在失去可读性的情况下)。基本上,我们选择是包含行还是排除行,这需要声明更少的元素(对于by创建的每个组)
    【解决方案2】:

    使用 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%&gt;% 是管道运算符)。这并不是说这不能提高效率。如果有人提出更高效和/或更易读的dplyr 解决方案,我很乐意再次更新。

    编辑 3

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

    dt %>%
        group_by(Name) %>%
        mutate(x = (ActivityType == 'Sale') ) %>%
        filter( !x[1],
                any(x),
                row_number() <= which.max(x))  %>%
        ungroup %>%
        select(-x)
    

    【讨论】:

    • 非常感谢您的回答!
    • 我相信你的答案可以通过翻译我的一些东西来改进(例如使用any而不是sum)。必须分别计算 3 次逻辑向量似乎也很浪费; dplyr 没有办法保存它并通过管道传递它吗?
    • 感谢 MichaelChirico 的建议。正如编辑中提到的%&gt;% 是 dplyr 使用的管道运算符(我相信最初来自“magrittr”包)。
    • 我不确定我们说的是同一件事;抱歉,我通过电话发送了该评论。您会注意到ActivityType == 'Sale' 是计算三个不同的时间。对此的一种解决方案是将#1 更改为:(x &lt;&lt;- ActivityType == 'Sale') &amp; GrpID == 1),然后将ActivityType == 'Sale' 替换为x。不过,这似乎也不太正确,我想知道是否有更多的dplyr-ish 方式来做到这一点。另外,如果我没记错的话,您可以将最后一行替换为 GrpID &lt;= which.max(ActivityType == 'Sale') (或 x 如果该方法有效)
    • @MichaelChirico 好的,明白了。我需要挖掘一下,看看是否有更有效的方法来做到这一点。不过有一条评论是,第二个过滤器只会看到第一个过滤器返回的数据子集(依此类推)。所以比较的大小与我的方式不同。
    【解决方案3】:
    # 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)
    

    【讨论】:

    • 非常感谢您的回答!
    猜你喜欢
    • 2019-03-26
    • 2020-09-06
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多