【问题标题】:Count rows (conditionally) within specified time period by group in R在R中按组计算指定时间段内的行(有条件地)
【发布时间】:2020-11-23 17:38:26
【问题描述】:

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

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

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

  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 解决方案,但我以前没有做过很多此类活动,所以不知道从哪里开始。非常感谢您提前:)

编辑

@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。这可能是这两种状态没有满足足够条件的情况。一如既往,我们将不胜感激!

【问题讨论】:

  • 你能dput最后两个有错误的例子吗:我想我更正了代码但想测试它。谢谢。

标签: r datetime count data-wrangling multiple-users


【解决方案1】:

您可以使用purrr 按用户拆分数据,并使用简单的 for 循环函数来实现您要查找的逻辑:

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) 似乎不足以产生可重现的结果。

【讨论】:

  • 非常感谢您的解决方案,它奏效了!我会花太多时间来想出我自己的解决方案,它的效果和这个一样好 :) 如果它允许我在 21 小时左右,我会给你 50 赏金声望点!
  • 我想我需要稍微修改一下代码。我从 5 小时阈值更改为 1 小时阈值。但是,我注意到在某些条目中,每个UserID 的调用之间间隔了几天。我认为它会继续寻找 StatusAnswered,而不是在前一个呼叫后 1 小时内没有另一个呼叫时为“尝试”创建一行。这就是我得到的:UserID OrigTime LastTime calls Status Successful 084272a5e6 2018-01-17 22:57:48 2018-01-19 20:28:32 2 Answered Y
  • 事实证明,在我上面的示例中,第 2 个调用中的第一个调用是“Engaged”。所以我应该得到的是状态为 Engaged & Success == N 的一行,以及呼叫为 Answered & Success == Y 的下一行。我想我需要在某处的代码中使用else 语句,但是它的样子是我需要@Waldi 的帮助
  • @Robin,今晚我会看看(CET)
  • 谢谢你,期待看到你的想法:)
猜你喜欢
  • 2021-05-21
  • 2021-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多