使用多个条件为每个用户创建累积计数器变量

时间:2016-08-11 15:40:32

标签: r data.table dplyr cumulative-sum

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

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

请考虑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

所需的计数器输出如下。 'output'是计数器变量。

    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

3 个答案:

答案 0 :(得分:4)

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

以下是使用data.table完成此操作的两种方法:

方法1:

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

此处的第二行仅标记1order_status == "t"的第一次出现。

我的一个评论很多的生产代码看起来像这样:

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

方法2:使用data.table的加入+更新

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

此处的第2步与方法1中的相同,但它直接标识第一个匹配项,并通过对基于连接的子集执行更新,将1更新为order_status == "t"。您可以使用DT替换内部的unique(DT),以便删除冗余。

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

答案 1 :(得分: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)

答案 2 :(得分:1)

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

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))

NB使用tidyr,您可以将mutate替换为replace_na(list(output = 0))