【问题标题】:Create cumulative counter variable per-user, with multiple conditions为每个用户创建具有多个条件的累积计数器变量
【发布时间】:2016-12-18 10:54:27
【问题描述】:

我需要根据其他三个变量创建一个计数器变量。

这是这个问题的扩展问题。extension question 考虑多个消费者在亚马逊下订单的情况。我想统计每个用户的成功订购次数。如果下单成功,计数器变量self加一;如果下单失败,计数器保持不变。显然,计数器变量将取决于时间、订单状态和用户。

请考虑t相同但订单状态不同的场景,这并不意味着行重复,它有其他列不同。

DT <- data.table(time=c(1,2,2,2,1,1,2,3,1,1),user=c(1,1,1,1,2,3,3,3,4,4), order_status=c('f','f','t','t','f','f','t','t','t','t'))
DT

所需的计数器输出如下。 “输出”是计数器变量。

    time user order_status output
 1:    1    1            f      0
 2:    2    1            f      0
 3:    2    1            t      1
 4:    2    1            t      1
 5:    1    2            f      0
 6:    1    3            f      0
 7:    2    3            t      1
 8:    3    3            t      2
 9:    1    4            t      1
10:    1    4            t      1

【问题讨论】:

    标签: r data.table dplyr cumulative-sum


    【解决方案1】:

    这里的主要挑战是将time, user, order_status=='t' 的每个组合的第一次出现设置为 1。然后它是一个由user 分组的简单累积和。

    这里有两种方法可以使用data.table 完成此操作:

    方法一:

    DT[, id := 0L
      ][order_status == "t", id := c(1L, rep(0L, .N-1L)), by=names(DT)
       ][, id := cumsum(id), by=user]
    

    这里的第二行仅在order_status == "t" 时标记1 的第一次出现。

    我的一个被大量评论的生产代码看起来像这样:

    DT[, id := 0L                       # set entire id col to 0
      ][order_status == "t",            # then, where order status is true
          id := c(1L, rep(0L, .N-1L)),  # set (or update) first value to 1
          by = names(DT)                # for every time,user,order_status
       ][, id := cumsum(id),            # then, get cumulative sum of id
           by = user]                   # for every user
    

    方法二:使用data.table的join+update

    DT[, id := 0L
      ][DT, id := as.integer(order_status == "t"), mult="first", on=names(DT)
       ][, id := cumsum(id), by=user]
    

    这里的第 2 步与方法 1 中的相同,但它直接识别第一次出现并通过对基于连接的子集执行更新将其更新为 1 if order_status == "t"。可以把里面的DT换成unique(DT),去掉冗余。

    如果我必须这样做,我会说第一种方法更有效,因为为每个组创建 rep() 应该非常快,而不是加入 + 更新。但我发现第二种方法更容易理解实际操作是什么,我认为如果你要在几周后查看你的代码,这更重要。

    【讨论】:

    • 可能which 不是连接,而是更快且同样可读? DT[, v := 0L][DT[order_status == "t", which=TRUE], v := rleid(time), by=user] rleid 很尴尬,但对我来说没有比 cumsum 更难理解的了。
    • c(1L, rep(0L, .N-1L) 中的“L”是什么?
    • @Frank,DT[order_status == "t", which=TRUE]就是which(order_status == "t"),相当于order_status == "t"(内部使用which)。另外,我认为rleid 在这里不适用,因为t 之后的f 应该保持t 下的值(尽管我不确定这是否是OP 的可能场景)。跨度>
    • @Phdaml 1 是 R 中的数字/双精度类型(占用 8 个字节的存储空间)。 1L 将类型设置为整数(4 个字节)。使用class(1)class(1L) 进行检查。
    【解决方案2】:

    使用data.table 的简单方法是:

    DT[,output := cumsum(order_status=="t" & !duplicated(cbind(time,user,order_status)))
       ,by=.(user)]
    
        time user order_status output
     1:    1    1            f      0
     2:    2    1            f      0
     3:    2    1            t      1
     4:    2    1            t      1
     5:    1    2            f      0
     6:    1    3            f      0
     7:    2    3            t      1
     8:    3    3            t      2
     9:    1    4            t      1
    10:    1    4            t      1
    

    这种方法基本上会为任何“f”值填充最后一个“t”值。如果您想将所有“f”值设为 0,这也很简单 - 只需将 by=... 更改为 by=.(user,order_status)

    【讨论】:

      【解决方案3】:

      最易读的方式可能是子查询。

      library(data.table)
      library(dplyr)
      DT <- data.table(time=c(1,2,2,2,1,1,2,3,1,1),user=c(1,1,1,1,2,3,3,3,4,4), order_status=c('f','f','t','t','f','f','t','t','t','t'))
      DT %>% left_join(
        DT %>%
          filter(order_status == "t") %>%
          group_by(user, time) %>%
          summarise() %>%
          arrange(time) %>%
          mutate(output = row_number()),
        by = c("user", "time")) %>%
        mutate(output = ifelse(is.na(output), 0, output))
      

      注意使用tidyr,您可以将最后一个mutate 替换为replace_na(list(output = 0))

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-08
        • 2013-08-31
        • 1970-01-01
        • 2019-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-11-16
        相关资源
        最近更新 更多