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

Create cumulative counter variable per-user, with multiple conditions

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

这是本题的扩展题。 考虑多个消费者在亚马逊下订单的情况。我想统计每个用户的订单成功次数。如果下单成功,计数器变量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

期望的计数器输出如下。 '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

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

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 你可以用 replace_na(list(output = 0)) 替换最后一个 mutate

使用 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).

这里的主要挑战是将 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

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

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

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

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