【问题标题】:Summarize values for overlapping time periods汇总重叠时间段的值
【发布时间】:2019-01-05 04:39:16
【问题描述】:

我正在尝试总结重叠时间段的值。 我只能使用 tidyr、ggplot2 和 dplyr 库。不过,Base 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)
}

...对于来自timestamps 的每个给定的date 遍历df 并给我一个timestamp 的值的总和。

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

【问题讨论】:

  • 你的样本数据有错误:2019-01-03 16:00:00 2019-01-02 08:00:00 4200,StopStart之前
  • 我在写我的帖子时没有注意到它,但不幸的是在这种情况下我正在处理它原本就在那里。有些数据是人为的,所以会发生这样的错误。

标签: r


【解决方案1】:

这是一个 tidyverse 解决方案,类似于我对 this recent question 的回复。我收集将时间戳(开始和停止)放入一列,另一列指定哪个。 Starts 将值相加,Stops 将其相减,然后我们只取累积和,即可得到总和变化时所有瞬间的值。

对于 100 条记录,使用 data.table 不会有任何明显的速度提升;根据我的经验,它开始对 100 万条记录产生更大的影响,尤其是在涉及分组时。

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

【讨论】:

  • 最后一行应该是cumsum(chg),而不是cumsum(Value)。否则总和列总是上升。但除此之外,这是一个不错且简单的解决方案。此外,正如@Wimpel 所注意到的,对于我的解决方案,应该有一个过滤器来删除不正确的数据:dt &lt;- dt[ Start &lt; Stop, ],我添加了它。
【解决方案2】:

过去我曾尝试使用 tidyverse/baseR 来解决类似的问题...但没有什么能比data.table 为此类操作提供的速度更接近,所以我鼓励你试一试...

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

我使用了您提供的示例数据,但过滤了 Stop &lt;= 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()

【讨论】:

  • 如果我将“1 天”更改为“1 小时”,这看起来是一个不错的解决方案,但我无法对其进行测试,因为我只能访问我提到的库。无论如何,谢谢你,我会在未来解决此类问题时意识到这一点。关于数据的正确性也很好,我已将您建议的过滤器添加到我的解决方案中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-06
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-13
相关资源
最近更新 更多