【问题标题】:Calculate time difference between two events while disregarding unmatched events计算两个事件之间的时间差,同时忽略不匹配的事件
【发布时间】:2017-08-31 19:54:39
【问题描述】:

我有一个结构如下的数据集:

structure(list(id = c(43956L, 46640L, 71548L, 71548L, 71548L, 
72029L, 72029L, 74558L, 74558L, 100596L, 100596L, 100596L, 104630L, 
104630L, 104630L, 104630L, 104630L, 104630L, 104630L, 104630L
), event = c("LOGIN", "LOGIN", "LOGIN", "LOGIN", "LOGOUT", "LOGIN", 
"LOGOUT", "LOGIN", "LOGOUT", "LOGIN", "LOGOUT", "LOGIN", "LOGIN", 
"LOGIN", "LOGIN", "LOGIN", "LOGIN", "LOGOUT", "LOGIN", "LOGOUT"
), timestamp = c("2017-03-27 09:19:29", "2016-06-10 00:09:08", 
"2016-01-27 12:00:25", "2016-06-20 11:34:29", "2016-06-20 11:35:44", 
"2016-12-28 10:43:25", "2016-12-28 10:56:30", "2016-10-15 15:08:39", 
"2016-10-15 15:10:06", "2016-03-09 14:30:48", "2016-03-09 14:31:10", 
"2017-04-03 10:36:54", "2016-01-11 16:52:08", "2016-02-03 14:40:32", 
"2016-03-30 12:34:56", "2016-05-26 13:14:25", "2016-08-22 15:20:02", 
"2016-08-22 15:21:53", "2016-08-22 15:22:23", "2016-08-22 15:23:08"
)), .Names = c("id", "event", "timestamp"), row.names = c(5447L, 
5446L, 5443L, 5444L, 5445L, 5441L, 5442L, 5439L, 5440L, 5436L, 
5437L, 5438L, 5425L, 5426L, 5427L, 5428L, 5429L, 5430L, 5431L, 
5432L), class = "data.frame")

         id  event           timestamp
5447  43956  LOGIN 2017-03-27 09:19:29
5446  46640  LOGIN 2016-06-10 00:09:08
5443  71548  LOGIN 2016-01-27 12:00:25
5444  71548  LOGIN 2016-06-20 11:34:29
5445  71548 LOGOUT 2016-06-20 11:35:44
5441  72029  LOGIN 2016-12-28 10:43:25
5442  72029 LOGOUT 2016-12-28 10:56:30
5439  74558  LOGIN 2016-10-15 15:08:39
5440  74558 LOGOUT 2016-10-15 15:10:06
5436 100596  LOGIN 2016-03-09 14:30:48
5437 100596 LOGOUT 2016-03-09 14:31:10
5438 100596  LOGIN 2017-04-03 10:36:54
5425 104630  LOGIN 2016-01-11 16:52:08
5426 104630  LOGIN 2016-02-03 14:40:32
5427 104630  LOGIN 2016-03-30 12:34:56
5428 104630  LOGIN 2016-05-26 13:14:25
5429 104630  LOGIN 2016-08-22 15:20:02
5430 104630 LOGOUT 2016-08-22 15:21:53
5431 104630  LOGIN 2016-08-22 15:22:23
5432 104630 LOGOUT 2016-08-22 15:23:08

我希望计算LOGINLOGOUT(会话持续时间)之间以及LOGOUTLOGIN(会话间隔)之间的时间差。不幸的是,我的 LOGIN 事件没有匹配的 LOGOUT 事件。

正确的LOGOUT 事件始终遵循其对应的LOGIN 事件(因为我根据idtimestamp 订购了数据框。我尝试调整this answer,但没有运气。我也尝试创建一个事件标识符,但由于我找不到让LOGOUT 事件的编号与LOGIN 事件的编号相匹配的方法,我不确定这样的标识符会有多大用处:

df$eventNum <- as.numeric(ave(as.character(df$id), df$id, as.character(df$event), FUN = seq_along))

【问题讨论】:

  • 您是否尝试过在 dplyr 包的 mutate 中使用 lag 函数?如果您按 id 和日期排序,然后按 id 分组,它应该可以工作
  • 如果我这样做,我会创建一个滞后的时间戳以及事件。然后我遇到的问题是创建一个变量,该变量定义当前时间戳与前一个时间戳之间的差异是会话持续时间还是间隔的一部分。我可以用ifelse() 声明来解决这个问题:statistic=ifelse(event==lastevent, NA, ifelse(event=="LOGIN", "duration", "interval"))。但是,这跨越了id 级别。

标签: r tidyverse


【解决方案1】:

这是我要采取的方法:

首先,我将event 变量转换为有序因子,因为以这种方式考虑它的值是有意义的(即,登录

df$event <- factor(df$event, levels = c("LOGIN", "LOGOUT"), ordered = T)

然后,假设 timestamp 是一种可行的格式,因为这将提供:

df$timestamp <- lubridate::parse_date_time(df$timestamp, "%Y-%m-%d %H:%M:%S")

您可以通过按 ID 分组,然后使用 ifelse 函数调用 mutate 来有条件地改变您的 data.frame:

df %>% group_by(id) %>% mutate(
  timeElapsed = ifelse(event != lag(event), lubridate::seconds_to_period(timestamp - lag(timestamp)), NA),
  eventType = ifelse(event > lag(event), 'Duration', ifelse(event < lag(event), 'Interval', NA))
  )
#        id  event           timestamp timeElapsed eventType
#     <int>  <ord>              <dttm>       <dbl>     <chr>
#  1  43956  LOGIN 2017-03-27 09:19:29          NA      <NA>
#  2  46640  LOGIN 2016-06-10 00:09:08          NA      <NA>
#  3  71548  LOGIN 2016-01-27 12:00:25          NA      <NA>
#  4  71548  LOGIN 2016-06-20 11:34:29          NA      <NA>
#  5  71548 LOGOUT 2016-06-20 11:35:44     1.25000  Duration
#  6  72029  LOGIN 2016-12-28 10:43:25          NA      <NA>
#  7  72029 LOGOUT 2016-12-28 10:56:30    13.08333  Duration
#  8  74558  LOGIN 2016-10-15 15:08:39          NA      <NA>
#  9  74558 LOGOUT 2016-10-15 15:10:06     1.45000  Duration
# 10 100596  LOGIN 2016-03-09 14:30:48          NA      <NA>
# 11 100596 LOGOUT 2016-03-09 14:31:10    22.00000  Duration
# 12 100596  LOGIN 2017-04-03 10:36:54    44.00000  Interval
# 13 104630  LOGIN 2016-01-11 16:52:08          NA      <NA>
# 14 104630  LOGIN 2016-02-03 14:40:32          NA      <NA>
# 15 104630  LOGIN 2016-03-30 12:34:56          NA      <NA>
# 16 104630  LOGIN 2016-05-26 13:14:25          NA      <NA>
# 17 104630  LOGIN 2016-08-22 15:20:02          NA      <NA>
# 18 104630 LOGOUT 2016-08-22 15:21:53    51.00000  Duration
# 19 104630  LOGIN 2016-08-22 15:22:23    30.00000  Interval
# 20 104630 LOGOUT 2016-08-22 15:23:08    45.00000  Duration

使用lubridate::seconds_to_period 将为您提供“%d %H %M %S”格式的时差。

【讨论】:

  • 当我在会话中运行您的确切代码时,它没有按 id 正确分组,导致跨 id 值计算持续时间和间隔。知道可能出了什么问题吗?
  • 嗯...这可能是命名空间冲突,如this answer 中所述。您是否可能有另一个加载了 group_by 函数的包?无论如何,您可以尝试使用dplyr::group_by()吗?
  • 好像不是这样。我完全糊涂了,因为这也发生在我之前自己做实验的时候。当我删除 group_by 语句时,我得到的输出与包含它时完全相同。我检查了我的数据框的结构,看看你的变量类型是否存在差异,但它们都完全相同。也不会干扰会话,因为在重新启动会话并仅运行示例所需的代码后会发生同样的问题。
  • 以下是我想到的其他一些长镜头:1) 尝试重新加载 R,但不是加载 tidyverse,而是加载 dplyr & lubridate,然后运行您的代码 2) 尝试重命名 @987654334 @ 变量到别的东西,看看分组是否仍然失败(这是一个真正的长镜头)和 3)检查你的 dplyr 和 tiyverse 版本(我使用的是 0.7.2 和 1.1.11),看看你是否正在使用较旧的软件包版本。
  • 另一个想法:在你的data.frame上测试这个df %&gt;% group_by(id) %&gt;% mutate(test = seq(n())),看看它是否正确地为你的分组行返回行编号。
【解决方案2】:

假设任何用户在注销之前都会无限期地保持登录状态,似乎可以通过某种方式对数据进行排序,这样一个简单的“lag”函数就可以解决问题。

使用库 dplyr 并假设您已将数据框称为“df”并且您已将时间戳转换为 日期格式,例如 POSIXct:

df %>% arrange(id,timestamp) %>%
  group_by(id,event)%>%
  mutate(rank = dense_rank(timestamp)) %>%
  ungroup() %>%
  arrange(id, rank,timestamp) %>%
  group_by(id)%>%
  mutate(duration = ifelse(event == "LOGOUT", timestamp- lag(timestamp),NA))

一行一行。

首先,我们按“id”和“timestamp”对数据进行排序,并按“id”和“event”分组以分配登录和注销事件的排名。同一用户的首次登录将具有“排名”1,该用户的首次注销也将具有“排名”1。

df %>% arrange(id,timestamp) %>%
  group_by(id,event)%>%
  mutate(rank = dense_rank(timestamp))

然后,我们删除数据的分组,并再次按 id、rank 和时间戳排序。这将产生一个具有正确顺序的数据帧,每个用户的 LOGIN 事件后跟 LOGOUT 事件,因此我们可以应用滞后计算。

  ungroup() %>%
  arrange(id, rank,timestamp) %>%

最后,我们再次按“id”分组,并使用 mutate 计算仅为 LOGOUT 事件的时间戳滞后。

  group_by(id)%>%
  mutate(duration = ifelse(event == "LOGOUT", timestamp- lag(timestamp),NA))

这应该会产生一个数据框,例如:

id  event           timestamp  rank     duration
    <int>  <chr>              <dttm> <int>        <dbl>
1   43956  LOGIN 2017-03-27 09:19:29     1           NA
2   46640  LOGIN 2016-06-10 00:09:08     1           NA
3   71548  LOGIN 2016-01-27 12:00:25     1           NA
4   71548 LOGOUT 2016-06-20 11:35:44     1 208715.31667
5   71548  LOGIN 2016-06-20 11:34:29     2           NA
6   72029  LOGIN 2016-12-28 10:43:25     1           NA
7   72029 LOGOUT 2016-12-28 10:56:30     1     13.08333
8   74558  LOGIN 2016-10-15 15:08:39     1           NA
9   74558 LOGOUT 2016-10-15 15:10:06     1      1.45000
10 100596  LOGIN 2016-03-09 14:30:48     1           NA
11 100596 LOGOUT 2016-03-09 14:31:10     1     22.00000

【讨论】:

  • 这也很好用! dense_rank() 非常棒,我还有其他可以用的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-21
  • 1970-01-01
  • 1970-01-01
  • 2016-09-18
  • 2020-10-29
相关资源
最近更新 更多