在 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
的呼叫次数,另外两个条件:-
- 如果在用户最后一次呼叫后的 5 小时内没有另一个呼叫,那么这将被视为一次“尝试”
- 如果用户在 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.table
或 dplyr
解决方案,但我以前没有做过很多这种 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) 似乎不足以产生可重现的结果。
我正在处理用户生成的数据,我想计算 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
的呼叫次数,另外两个条件:-
- 如果在用户最后一次呼叫后的 5 小时内没有另一个呼叫,那么这将被视为一次“尝试”
- 如果用户在 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.table
或 dplyr
解决方案,但我以前没有做过很多这种 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) 似乎不足以产生可重现的结果。