【问题标题】:Creating intervals from time series data从时间序列数据创建间隔
【发布时间】:2018-10-10 10:51:28
【问题描述】:

我有一个用户和访问时间数据框。访问可以重复。 我正在尝试创建一个用户分组列表,并以给定的时间间隔命名,例如年。

timestamp user
1  2013-03-06 01:00:00    1
2  2014-07-06 21:00:00    1
3  2014-07-31 23:00:00    2
4  2014-08-09 17:00:00    2
5  2014-08-14 20:00:00    2
6  2014-08-14 22:00:00    3
7  2014-08-16 15:00:00    3
8  2014-08-19 02:00:00    1
9  2014-12-28 18:00:00    1
10 2015-01-17 17:00:00    1
11 2015-01-22 22:00:00    2
12 2015-01-22 22:00:00    3
13 2015-03-23 15:00:00    4
14 2015-04-05 18:00:00    1
15 2015-04-06 01:00:00    2 

我的代码示例已经创建了一个按年份分组的用户列表。 我的问题是我需要在这种方法中修改 表格,这对我的 百万条目的表格来说是个问题。

test <- structure(list(timestamp = c("2013-03-06 01:00:00", "2014-07-06 21:00:00", 
                                 "2014-07-31 23:00:00", "2014-08-09 17:00:00", "2014-08-14 20:00:00", 
                                 "2014-08-14 22:00:00", "2014-08-16 15:00:00", "2014-08-19 02:00:00", 
                                 "2014-12-28 18:00:00", "2015-01-17 17:00:00", "2015-01-22 22:00:00", 
                                 "2015-01-22 22:00:00", "2015-03-23 15:00:00", "2015-04-05 18:00:00", 
                                 "2015-04-06 01:00:00"), user = c(1L, 1L, 2L, 2L, 2L, 3L, 3L, 
                                                                  1L, 1L, 1L, 2L, 3L, 4L, 1L, 2L)), .Names = c("timestamp", "user"
                                                                  ), class = "data.frame", row.names = c(NA, -15L))

require(lubridate)
#Creating "POSIXct" object from string timestamp
timestamp <- lapply(test$timestamp,
                function(x)parse_date_time(x, "y-m-d H:M:S"))
test$timestamp <- do.call(c,timestamp)
print(class(test$timestamp))

#Adding column for year
test <- cbind(test,sapply(timestamp, function(x)year(x)))
colnames(test)[3]<- "year"

#Creating list of year time intervals for users
intervals <- names(table(test$year))
users <- lapply(intervals, function(x)test[test$year %in% x,"user"])
names(users) <- intervals

【问题讨论】:

    标签: r lubridate


    【解决方案1】:

    没有时间戳

    timestamp 视为一个字符。仅适用于每个时间戳的前 4 位数字代表年份。

    library(dplyr)
    test %>%
      group_by( user, substr(timestamp,1,4 ) ) %>%
      summarise( )
    
    #    user `substr(timestamp, 1, 4)`
    #   <int> <chr>                    
    # 1     1 2013                     
    # 2     1 2014                     
    # 3     1 2015                     
    # 4     2 2014                     
    # 5     2 2015                     
    # 6     3 2014                     
    # 7     3 2015                     
    # 8     4 2015
    

    dplyr + lubridate

    将从时间戳中提取年份

    library( dplyr )
    library( lubridate )
    test %>%
      mutate( timestamp = as.POSIXct( timestamp, format = "%Y-%m-%d %H:%M:%S" ) ) %>%
      group_by( user, lubridate::year( timestamp ) ) %>%
      summarise( )
    
    # # Groups:   user [?]
    #    user `year(timestamp)`
    #   <int>             <dbl>
    # 1     1              2013
    # 2     1              2014
    # 3     1              2015
    # 4     2              2014
    # 5     2              2015
    # 6     3              2014
    # 7     3              2015
    # 8     4              2015
    

    表格

    频率表也很快制作出来了

    table( test$user, substr( test$timestamp, 1, 4 ) )
    
    #   2013 2014 2015
    # 1    1    3    2
    # 2    0    3    2
    # 3    0    2    1
    # 4    0    0    1
    

    还有其他选择...选择一个

    编辑

    如果速度是个问题,ty data.table

    dcast( 
      setDT( test )[, timestamp :=  as.POSIXct( timestamp, format = "%Y-%m-%d %H:%M:%S" )][, .N, by = list( user, data.table::year(timestamp) )],
      user ~ data.table,
      value.var = "N")
    
    #    user 2013 2014 2015
    # 1:    1    1    3    2
    # 2:    2   NA    3    2
    # 3:    3   NA    2    1
    # 4:    4   NA   NA    1
    

    【讨论】:

    • 这是更通用的解决方案 - 可以使用月、日、...轻松修改
    【解决方案2】:

    另一个使用闪电般快速data.table 包的选项:

    library(data.table)
    setDT(test) # make `test` a data.frame 'by reference' (no copy is made at all)
    
    test[, j=.(users=list(unique(user))),
           by=.(year=substr(test$timestamp,1,4))] 
    
       year   users
    1: 2013       1
    2: 2014   1,2,3
    3: 2015 1,2,3,4
    

    再次假设您的 test$timestamp 列是字符向量 - 否则根据需要替换 lubridate::year()。

    更新:

    按月显示分组的简单更改(正如评论中提到的那样):

     test[, j=.(users=list(unique(user))),
            by=.(ym=substr(test$timestamp,1,7))] 
    
            ym users
    1: 2013-03     1
    2: 2014-07   1,2
    3: 2014-08 2,3,1
    4: 2014-12     1
    5: 2015-01 1,2,3
    6: 2015-03     4
    7: 2015-04   1,2
    

    或按天分组,以帮助演示如何使用链接进行子集化:

    test[, j=.(users=list(unique(user))),
           by=.(ymd=substr(test$timestamp,1,11))][ymd>='2014-08-01' & ymd<= '2014-08-21']
    
               ymd users
    1: 2014-08-09      2
    2: 2014-08-14    2,3
    3: 2014-08-16      3
    4: 2014-08-19      1
    

    注意过滤/子集,如果您只对“一次性”计算的日期子集感兴趣(而不是保存整个聚合集以用于其他目的),这样做可能会更有效DT[i, j, by]i 中的子集,用于“一次性”计算。

    【讨论】:

      【解决方案3】:

      您还可以使用基本(统计)函数aggregate(),如下所示:

      aggregate( x = test$user, 
                 by = list(year=substr(test$timestamp,1,4)), 
                 FUN = unique ) 
      

      结果:

        year          x
      1 2013          1
      2 2014    1, 2, 3
      3 2015 1, 2, 3, 4
      

      以上假设您的时间戳列最初只是一个字符向量,与您的结构化示例数据中包含的完全相同。在这种情况下,您可以直接使用 substr(test$timestamp,1,4) 来替代年份,而无需先转换为日期。

      但是,如果您已经将时间戳列作为日期,只需替换您在尝试的解决方案中演示的 lubridate::year() 函数。

      【讨论】:

      • 同意这不是您可能获得的最快解决方案。但只是好奇,因为aggregate() 实际上速度非常快,您可以根据原始解决方案尝试使用您的数据并发布时间吗?这将为其他人提供有趣的比较和有用的基准。