汇总重叠时间段的值

时间:2019-01-05 04:39:17

标签: r

我正在尝试汇总重叠时间段的值。 我只能使用tidyr,ggplot2和dplyr库。不过最好使用基数R。

我的数据看起来像这样,但通常有大约100条记录:

df <- structure(list(Start = structure(c(1546531200, 1546531200, 546531200, 1546638252.6316, 1546549800, 1546534800, 1546545600, 1546531200, 1546633120, 1547065942.1053), class = c("POSIXct", "POSIXt"), tzone = "UTC"), Stop = structure(c(1546770243.1579, 1546607400, 1547110800, 1546670652.6316, 1547122863.1579, 1546638252.6316, 1546878293.5579, 1546416000, 1546849694.4, 1547186400), class = c("POSIXct", "POSIXt"), tzone = "UTC"), Value = c(12610, 520, 1500, 90, 331380, 27300, 6072, 4200, 61488, 64372)), .Names = c("Start", "Stop", "Value"), row.names = c(41L, 55L, 25L, 29L, 38L, 28L, 1L, 20L, 14L, 31L), class = c("tbl_df", "tbl", "data.frame"))

head(df)str(df)给出:

          Start                Stop      Value
2019-01-03 16:00:00 2019-01-06 10:24:03  12610
2019-01-03 16:00:00 2019-01-04 13:10:00    520
2019-01-03 16:00:00 2019-01-10 09:00:00   1500
2019-01-04 21:44:12 2019-01-05 06:44:12     90
2019-01-03 21:10:00 2019-01-10 12:21:03 331380
2019-01-03 17:00:00 2019-01-04 21:44:12  27300

Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   10 obs. of  3 variables:
 $ Start: POSIXct, format: "2019-01-03 16:00:00" "2019-01-03 16:00:00" ...
 $ Stop : POSIXct, format: "2019-01-06 10:24:03" "2019-01-04 13:10:00" ...
 $ Value: num  12610 520 1500 90 331380 ...

因此存在重叠的时间段,其中“开始”和“停止”日期具有分配的值。在任何给定记录中,当df$Startdf$Stop之间有一个值并且在此范围之外时,该值为0。

我想创建另一个数据框,以此为基础,我可以显示该值如何汇总和随时间变化。所需的输出将如下所示(“ sum”列组成):

> head(df2)
              timestamp     sum
"2019-01-02 09:00:00 CET"   14352
"2019-01-03 17:00:00 CET"   6253
"2019-01-03 18:00:00 CET"   23465
"2019-01-03 21:00:00 CET"   3241
"2019-01-03 22:10:00 CET"   23235
"2019-01-04 14:10:00 CET"   123321

要获取唯一的时间戳记,

timestamps <- sort(unique(c(df$`Start`, df$`Stop`)))

借助df2数据框,我可以轻松地用ggplot绘制图形,但是如何获得总和?

我认为我应该对df数据帧进行迭代,或者使用一些自定义函数或任何内置的摘要函数,其工作方式如下:

fnct <- function(date, min, max, value) {
  if (date >= min && date <=max) {
    a <- value
  }
  else {
  a <- 0
  }
  return(a)
}

...对于date中的每个给定timestamps进行迭代,通过df并给我timestamp的值的总和。

它看起来非常简单,我缺少一些非常基本的东西。

2 个答案:

答案 0 :(得分:0)

过去,我曾尝试使用tidyverse / baseR解决类似的问题...但是,data.table所提供的这些操作的速度还远远不能与之相提并论,因此,我鼓励您使用它尝试...

对于这样的问题,我最喜欢的功能是foverlaps()包中的data.table。使用此功能,您可以(快速!)执行重叠连接。如果您希望加入时比foverlaps()提供更多的灵活性,那么non-equi-join(再次使用data.table)可能是最好(也是最快!)的选择。但是foverlaps()会在这里做(我想)。

我使用了您提供的示例数据,但过滤掉了Stop <= Start处的行(可能是示例数据中的一个分型)。如果df$Start不在df$Stop之前,foverlaps会发出警告,并且不会执行。

library( data.table )

#create data.table with periods you wish to simmarise on
#NB: UTC is used as timezone, since this is also the case in the sample data provided!!
dt.dates <- data.table( id = paste0( "Day", 1:31 ),
                        Start = seq( as.POSIXct( "2019-01-01 00:00:00", format = "%Y-%m-%d %H:%M:%S", tz = "UTC" ),
                                     as.POSIXct( "2019-01-31 00:00:00", format = "%Y-%m-%d %H:%M:%S", tz = "UTC" ),
                                     by = "1 days"),
                        Stop = seq( as.POSIXct( "2019-01-02 00:00:00", format = "%Y-%m-%d %H:%M:%S", tz = "UTC" ) - 1,
                                    as.POSIXct( "2019-02-01 00:00:00", format = "%Y-%m-%d %H:%M:%S", tz = "UTC" ) - 1,
                                    by = "1 days") )

如果您不想每天进行汇总,而是按每年的小时,分​​钟,秒进行汇总。只需更改dt.dates data.table中的值(并逐步调整大小),以使其与您的期间相符即可。

#set df as data.table
dt <- as.data.table( df )
#filter out any row where Stop is smaller than Start
dt <- dt[ Start < Stop, ]

#perform overlap join
#first set keys
setkey(dt, Start, Stop)
#then perform join
result <- foverlaps( dt.dates, dt, type = "within" )
#summarise
result[, .( Value = sum( Value , na.rm = TRUE ) ), by = .(Day = i.Start) ]

输出

#            Day  Value
#  1: 2019-01-01   1500
#  2: 2019-01-02   1500
#  3: 2019-01-03   1500
#  4: 2019-01-04 351562
#  5: 2019-01-05 413050
#  6: 2019-01-06 400440
#  7: 2019-01-07 332880
#  8: 2019-01-08 332880
#  9: 2019-01-09 332880
# 10: 2019-01-10  64372
# 11: 2019-01-11      0
# 12: 2019-01-12      0
# 13: 2019-01-13      0
# 14: 2019-01-14      0
# 15: 2019-01-15      0
# 16: 2019-01-16      0
# 17: 2019-01-17      0
# 18: 2019-01-18      0
# 19: 2019-01-19      0
# 20: 2019-01-20      0
# 21: 2019-01-21      0
# 22: 2019-01-22      0
# 23: 2019-01-23      0
# 24: 2019-01-24      0
# 25: 2019-01-25      0
# 26: 2019-01-26      0
# 27: 2019-01-27      0
# 28: 2019-01-28      0
# 29: 2019-01-29      0
# 30: 2019-01-30      0
# 31: 2019-01-31      0
#            Day  Value

情节

#summarise for plot
result.plot <- result[, .( Value = sum( Value , na.rm = TRUE ) ), by = .(Day = i.Start) ]
library( ggplot2 )
ggplot( data = result.plot, aes( x = Day, y = Value ) ) + geom_col()

enter image description here

答案 1 :(得分:0)

这是一个整洁的解决方案,类似于我对this recent question的答复。我聚集在一起将时间戳记(开始和停止)放在一列中,另一列指定了时间戳。 Starts将值相加,Stops将其相减,然后我们只求累加总和即可在总和发生变化的所有时刻获取值。

对于100条记录,使用data.table不会显着提高速度;以我的经验,它开始对1M记录产生更大的影响,尤其是在涉及分组时。

library(dplyr); library(tidyr)
df2 <- df %>%
  gather(type, time, Start:Stop) %>%
  mutate(chg = if_else(type == "Start", Value, -Value)) %>%
  arrange(time) %>%
  mutate(sum = cumsum(chg)) # EDIT: corrected per OP comment

> head(df2)
## A tibble: 6 x 5
#  Value type  time                   chg    sum
#  <dbl> <chr> <dttm>               <dbl>  <dbl>
#1  1500 Start 1987-04-27 14:13:20   1500   1500
#2  4200 Stop  2019-01-02 08:00:00  -4200  -2700
#3 12610 Start 2019-01-03 16:00:00  12610   9910
#4   520 Start 2019-01-03 16:00:00    520  10430
#5  4200 Start 2019-01-03 16:00:00   4200  14630
#6 27300 Start 2019-01-03 17:00:00  27300  41930