在 R 中按组对指定时间段内的行(有条件地)进行计数

Count rows (conditionally) within specified time period by group in R

我正在处理用户生成的数据,我想计算 rows/activities 的次数,即每个用户在特定时间段内进行的调用。这是一个模拟数据框,类似于我正在使用的数据框:

library(ids)#for generating the UserID variable
library(wakefield)#for generating the Status variable
library(dplyr)

set.seed(123)
UserID<-random_id(n=10, bytes = 5)
DateTime<-seq.POSIXt(from = as.POSIXct("2020-08-01 01:00:00", tz = Sys.timezone()), length.out = 70, by = "15 mins")
df<-cbind(UserID,DateTime)
df<-as.data.frame(df)
df$Status<-r_sample_factor(x = c("Answered", "Abandoned", "Engaged"), n=70)
df$DateTime<-seq.POSIXt(from = as.POSIXct("2020-08-01 01:00:00", tz = Sys.timezone()), 
                        length.out = 70, by = "15 mins")#re-doing this again as it annoyingly converts to numeric each time 

df<-df%>%arrange(UserID,DateTime)
head(df)


      #UserID            DateTime    Status
#1 0a5f3a2a8b 2020-08-01 02:00:00   Engaged
#2 0a5f3a2a8b 2020-08-01 04:30:00   Engaged
#3 0a5f3a2a8b 2020-08-01 07:00:00   Engaged
#4 0a5f3a2a8b 2020-08-01 09:30:00   Engaged
#5 0a5f3a2a8b 2020-08-01 12:00:00   Engaged
#6 0a5f3a2a8b 2020-08-01 14:30:00 Abandoned

我想做的是计算 5 小时内 UserID 的呼叫次数,另外两个条件:-

  1. 如果在用户最后一次呼叫后的 5 小时内没有另一个呼叫,那么这将被视为一次“尝试”
  2. 如果用户在 5 小时内有 N 个呼叫直到他们得到“已应答”,则这算作“成功”尝试。否则,它将作为“不成功”

这是我想要实现的目标:-

UserId          OrigTime       LastTime          Calls  Status       Successful
0a5f3a2a8b  2020-08-01 02:00:00 2020-08-01 07:00:00 3   Engaged          No
16db61d2bc  2020-08-01 03:15:00 2020-08-01 03:15:00 1   Answered         Yes
6355f7700d  2020-08-01 01:00:00 2020-08-01 06:00:00 3   Answered         Yes
9b9fab9789  2020-08-01 04:15:00 2020-08-01 09:15:00 3   Answered         Yes
...

所以 OrigTime 是他们在单次尝试中第一次调用的时间,LastTime 是他们在同一次尝试中最后一次调用的时间。 Calls 列计算用户在该尝试中进行的调用次数,Status 是尝试中最后一次调用的状态,“成功”可以是合乎逻辑的,表示该尝试中的最后一次调用是否是否回答。

任何正确方向的指示都会很棒。我想象有一些 data.tabledplyr 解决方案,但我以前没有做过很多这种 activity,所以不确定从哪里开始。非常感谢您:)

编辑

@Waldi 提供了一个几乎满足我需要的解决方案。这是迄今为止效果最好的解决方案(根据@Waldi 的回答略作修改):-

CondCount <- function(data,maxdelay){
  result <- list()
  row <- 0
  calls <- 0
  OrigTime <- NA
  n <- nrow(data)
  
  for (i in 1:n) {
    if (is.na(OrigTime)) {
      OrigTime <- data$DateTime[[i]]
      calls <- 0
    }
    calls = calls + 1
    if (data$Status[[i]] == "Answered" | difftime(data$DateTime[[i]],OrigTime,units='hours') > maxdelay | i==n) {
      row <- row + 1
      result[[row]] <- data.frame(OrigTime = OrigTime, LastTime = data$DateTime[[i]], calls = calls, Status = factor(data$Status[[i]],levels=c("Answered" ,"Abandoned" ,"Engaged","Unknown")), Successful = ifelse(data$Status[[i]]=="Answered",'Y','N')  )
      OrigTime <- NA
    }
  }
  dplyr::bind_rows(result)
}

df %>% arrange(UserID,DateTime) %>%
       split(.$UserID) %>%
       map(function(data) {CondCount(data,1) }) %>%
       bind_rows(.id="UserID") 


请查看我在编辑之前写的 2 个步骤。这一次,周期是 1 小时 而不是 5 小时.

使用@Waldi 的解决方案,这是它在我的真实 df 上工作的时间(对于我使用的颜色编码,如果碰巧有任何色盲 SO 用户,我深表歉意):-

正确结果

使用@Waldi 的解决方案,它会给你这个:-

正确!这就是我的目标。但是,我想举例说明当我 运行 这段代码时会发生什么,这会产生不希望的结果:-

不正确的结果 1

这给你这个:-

这是不正确的。它应该是两行,每次尝试一行(每次最终状态为“已放弃”),而不是一行,因为最后两行之间的时间差大于 60 分钟。

不正确的结果 2

这给你这个:-

这是不正确的。它应该是两行,每次尝试一行(第一行状态为“已参与”,第二行状态为“已回答”)。

我必须非常感谢@Waldi,因为该解决方案非常适合接听电话。但是,它没有考虑其他状态类型,即 Abandoned 和 Engaged。这两个状态可能是条件不够满足的情况。一如既往,我们将不胜感激!

您可以使用 purrr 按用户拆分数据,并使用一个简单的 for-loop 函数来实现您正在寻找的逻辑:

library(purrr)

CondCount <- function(data,maxdelay){
  result <- list()
  row <- 0
  calls <- 0
  OrigTime <- NA
  n <- nrow(data)
  
  for (i in 1:n) {
    if (is.na(OrigTime)) {
      OrigTime <- data$DateTime[[i]]
      calls <- 0
    }
    calls = calls + 1
    if (difftime(data$DateTime[[i]],OrigTime,units='hours') > maxdelay) {
      row <- row + 1
      result[[row]] <- data.frame(OrigTime = OrigTime, LastTime = data$DateTime[[i-1]], calls = calls, Status = factor(data$Status[[i-1]],levels=c("Answered" ,"Abandoned" ,"Engaged")), Successful = ifelse(data$Status[[i]]=="Answered",'Y','N')  )
      OrigTime <- data$DateTime[[i]]
    } 
    if ((data$Status[[i]] !="Engaged") | i == n) {
      row <- row + 1
      result[[row]] <- data.frame(OrigTime = OrigTime, LastTime = data$DateTime[[i]], calls = calls, Status = factor(data$Status[[i]],levels=c("Answered" ,"Abandoned" ,"Engaged")), Successful = ifelse(data$Status[[i]]=="Answered",'Y','N')  )
      OrigTime <- NA
    }
  } 
  dplyr::bind_rows(result)
}



df %>% arrange(UserID,DateTime) %>%
  split(.$UserID) %>%
  map(function(data) {CondCount(data,5) }) %>%
  bind_rows(.id="UserID")

       UserID            OrigTime            LastTime calls    Status Successful
1  022098d3cf 2020-08-01 03:15:00 2020-08-01 03:15:00     1  Answered          Y
2  022098d3cf 2020-08-01 05:45:00 2020-08-01 05:45:00     1  Answered          Y
3  022098d3cf 2020-08-01 08:15:00 2020-08-01 08:15:00     1 Abandoned          N
4  022098d3cf 2020-08-01 10:45:00 2020-08-01 10:45:00     1  Answered          Y
5  022098d3cf 2020-08-01 13:15:00 2020-08-01 13:15:00     1 Abandoned          N
6  022098d3cf 2020-08-01 15:45:00 2020-08-01 15:45:00     1 Abandoned          N
7  022098d3cf 2020-08-01 18:15:00 2020-08-01 18:15:00     1 Abandoned          N
8  18f13c3972 2020-08-01 01:15:00 2020-08-01 03:45:00     2 Abandoned          N
9  18f13c3972 2020-08-01 06:15:00 2020-08-01 06:15:00     1  Answered          Y
10 18f13c3972 2020-08-01 08:45:00 2020-08-01 13:45:00     3  Answered          Y

如果循环需要非常快,可以很容易地转换为Rcpp

注意:出于某种原因,set.seed(123) 似乎不足以产生可重现的结果。