使用dplyr将数据按照每n分钟分组

17

我有一个数据集,其中包含在给定日期的特定时间发生的10个事件,并且每个事件都有相应的值:

d1 <- data.frame(date = as.POSIXct(c("21/05/2010 19:59:37", "21/05/2010 08:40:30", 
                            "21/05/2010 09:21:00", "21/05/2010 22:29:50", "21/05/2010 11:27:34", 
                            "21/05/2010 18:25:14", "21/05/2010 15:16:01", "21/05/2010 09:41:53", 
                            "21/05/2010 15:01:29", "21/05/2010 09:02:06"), format ="%d/%m/%Y %H:%M:%S"),
                 value = c(11313,42423,64645,643426,1313313,1313,3535,6476,11313,9875))

我希望每3分钟汇总一次结果,以标准的数据框格式呈现(从“2010年5月21日 00:00:00”到“2010年5月21日 23:57:00”,使数据框具有480个3分钟的bin)。

首先,我创建一个数据框,其中包含每个3分钟的bin:

d2 <- data.frame(date = seq(as.POSIXct("2010-05-21 00:00:00"), 
                            by="3 min", length.out=(1440/3)))

然后,我将这两个数据框合并在一起,并删除NA值:

library(dplyr)
m <- merge(d1, d2, all=TRUE) %>% mutate(value = ifelse(is.na(value),0,value))

最后,我使用来自xts包的period.apply()函数对每个“bin”中的值求和:

library(xts)
a <- period.apply(m$value, endpoints(m$date, "minutes", 3), sum)

有没有更有效的方法来完成这个任务?它感觉不够优化。

更新 #1

在 Joshua 的回答后,我调整了我的代码:

library(xts)
startpoints <- function (x, on = "months", k = 1) { 
  head(endpoints(x, on, k) + 1, -1) 
}

m <- seq(as.POSIXct("2010-05-21 00:00:00"), by="3 min", length.out=1440/3)
x <- merge(value=xts(d1$value, d1$date), xts(,m))
y <- period.apply(x, c(0,startpoints(x, "minutes", 3)), sum, na.rm=TRUE)

我之前不知道period.apply()可以使用na.rm=TRUE,现在可以跳过mutate(value = ifelse(is.na(value),0,value))。这是一步向前迈进,我很满意这里的xts方法,但我想知道是否有一个纯粹的dplyr解决方案可以在这种情况下使用。

更新#2

在尝试Khashaa的答案后,我因为没有指定时区而出现错误。所以我有:

> tail(d4)
               interval sumvalue
476 2010-05-21 23:45:00       NA
477 2010-05-21 23:48:00       NA
478 2010-05-21 23:51:00       NA
479 2010-05-21 23:54:00       NA
480 2010-05-21 23:57:00    11313
481 2010-05-22 02:27:00   643426
> d4[450,]
               interval sumvalue
450 2010-05-21 22:27:00       NA

现在,在执行Sys.setenv(TZ="UTC")之后,所有问题都已解决。

4个回答

12

lubridate-dplyr 类似的解决方案。

library(lubridate)
library(dplyr)
d2 <- data.frame(interval = seq(ymd_hms('2010-05-21 00:00:00'), by = '3 min',length.out=(1440/3)))
d3 <- d1 %>% 
  mutate(interval = floor_date(date, unit="hour")+minutes(floor(minute(date)/3)*3)) %>% 
  group_by(interval) %>% 
  mutate(sumvalue=sum(value))  %>% 
  select(interval,sumvalue) 
d4 <- merge(d2,d3, all=TRUE) # better if left_join is used
tail(d4)
#               interval sumvalue
#475 2010-05-21 23:42:00       NA
#476 2010-05-21 23:45:00       NA
#477 2010-05-21 23:48:00       NA
#478 2010-05-21 23:51:00       NA
#479 2010-05-21 23:54:00       NA
#480 2010-05-21 23:57:00       NA
d4[450,]
#               interval sumvalue
#450 2010-05-21 22:27:00   643426

如果您习惯于使用Date(我不习惯),则可以放弃lubridate,并将最后一个合并替换为left_join


1
通过这个解决方案,我得到了第481行,其中“interval”为“2010-05-22 02:27:00”,“value”为“643426”。 - Steven Beaupré
我刚在一个新的会话中运行了它,仍然得到了相同的结果。我不明白为什么你得到了不同的结果。 - Khashaa
5
这是一个时区问题。在原文中,您调用了 as.POSIXct 但没有指定时区,因此它会使用您的本地时区;而 Khashaa 使用的是 lubridate::ymd_hms,如果您不指定时区,它会默认使用 UTC 时区。如果您在运行定义 d1 的代码之前调用 Sys.setenv(TZ="UTC"),则会得到与 Khashaa 相同的结果。请注意,这里不能进行解释或返回任何其他内容。 - GSee
1
@GSee 是的,那解决了。谢谢 :) - Steven Beaupré
@GSee,真的很有道理。感谢你澄清了事情。 - Khashaa

11
如果您需要将数据分组为n分钟的区间,可以使用floor_date函数在unit参数中指定多个单位。例如:
library(lubridate)
x <- ymd_hms("2009-08-03 12:25:59.23")
floor_date(x, unit = "3minutes")

"2009-08-03 12:24:00 UTC"

使用您提供的示例:

library(lubridate)
library(tidyverse)

# make complete time sequence
d2 <- data.frame(timePeriod = seq(as.POSIXct("2010-05-21 00:00:00"), 
                        by="3 min", length.out=(1440/3)))

d1 %>%
  mutate(timePeriod = floor_date(date, "3minutes")) %>%
  group_by(timePeriod) %>%
  summarise(sum = sum(value)) %>%
  right_join(d2)

2
这太干净了!不再需要那些xts的花招了!谢谢! - philiporlando
2
这是我个人认为最优美的解决方案。 - grssnbchr

8

我不确定是否有dplyr的解决方案,但是这里有一个xts的解决方案:

startpoints <- function (x, on = "months", k = 1) {
  head(endpoints(x, on, k) + 1, -1)
}
m3 <- seq(as.POSIXct("2010-05-21 00:00:00"),
  by="3 min", length.out=1440/3)
x <- merge(value=xts(d1$value, d1$date), xts(,m3))
y <- period.apply(x, c(0,startpoints(x, "minutes", 3)), sum, na.rm=TRUE)
< p > < em > 更新 :这里有另一种 xts 解决方案,它更加小心地对齐聚合值。并不是说之前的解决方案是错误的,但是这个解决方案更易于理解,并且可以在其他分析中重复使用。

m3 <- seq(as.POSIXct("2010-05-20 23:59:59.999"),
  by="3 min", length.out=1440/3)
x <- merge(value=xts(d1$value, d1$date), xts(,m3))
y <- period.apply(x, endpoints(x, "minutes", 3), sum, na.rm=TRUE)
y <- align.time(y, 60*3)

3
最近,开发了padr包,可以以简洁的方式解决这个问题。
library(lubridate)
library(dplyr)
library(padr)

d1 <- data.frame(date = as.POSIXct(c("21/05/2010 19:59:37", "21/05/2010 08:40:30", 
                                     "21/05/2010 09:21:00", "21/05/2010 22:29:50", "21/05/2010 11:27:34", 
                                     "21/05/2010 18:25:14", "21/05/2010 15:16:01", "21/05/2010 09:41:53", 
                                     "21/05/2010 15:01:29", "21/05/2010 09:02:06"), format ="%d/%m/%Y %H:%M:%S"),
                 value = c(11313,42423,64645,643426,1313313,1313,3535,6476,11313,9875))

res <- d1 %>% 
  as_tibble() %>%
  arrange(date) %>%

  # Thicken the results to fall in 3 minute buckets
  thicken(
    interval  = '3 min', 
    start_val = as.POSIXct('2010-05-21 00:00:00'),
    colname   = "date_pad") %>% 

  # Pad the results to fill in the rest of the 3 minute buckets
  pad(
    interval  = '3 min', 
    by        = 'date_pad', 
    start_val = as.POSIXct('2010-05-21 00:00:00'),
    end_val   = as.POSIXct('2010-05-21 23:57:00')) %>%

  select(date_pad, value)

res
#> # A tibble: 480 x 2
#>    date_pad            value
#>    <dttm>              <dbl>
#>  1 2010-05-21 00:00:00    NA
#>  2 2010-05-21 00:03:00    NA
#>  3 2010-05-21 00:06:00    NA
#>  4 2010-05-21 00:09:00    NA
#>  5 2010-05-21 00:12:00    NA
#>  6 2010-05-21 00:15:00    NA
#>  7 2010-05-21 00:18:00    NA
#>  8 2010-05-21 00:21:00    NA
#>  9 2010-05-21 00:24:00    NA
#> 10 2010-05-21 00:27:00    NA
#> # ... with 470 more rows

res[450,]
#> # A tibble: 1 x 2
#>   date_pad             value
#>   <dttm>               <dbl>
#> 1 2010-05-21 22:27:00 643426

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接