将时间四舍五入到最近的15分钟

37

我有一个 POSIXct 值的向量,我想将它们四舍五入到最近的 15 分钟。我不关心日期。如何将这些值转换为小时和分钟?

例如,我希望将值

"2012-05-30 20:41:21 UTC"

成为

"20:45"
7个回答

37

确实,这是一个有一些有用的回答的老问题。到目前为止,giraffhere的最后一个回答似乎是最优雅的。然而,解决问题的不是floor_date而是round_date:

lubridate::round_date(x, "15 minutes") 

28
你可以使用 round 函数。诀窍在于在舍入之前将数值除以900秒(15分钟*60秒),然后在舍入后乘以900秒:
a <-as.POSIXlt("2012-05-30 20:41:21 UTC")
b <-as.POSIXlt(round(as.double(a)/(15*60))*(15*60),origin=(as.POSIXlt('1970-01-01')))
b
[1] "2012-05-30 20:45:00 EDT"

要仅获取小时和分钟,只需使用格式

format(b,"%H:%M")
[1] "20:45"

as.character(format(b,"%H:%M"))
[1] "20:45"

5
我认为我们不需要使用双精度浮点数,因为as.POSIXlt函数可以接受一个字符串作为参数。稍微简化一下就是:b <- as.POSIXlt(round(as.numeric(a)/(15*60))*(15*60), origin='1970-01-01') - Mark Rajcok
自从R4.3.*版本以后,如果是Unix纪元的情况下,我们不再需要显式地指定起始点。 - jay.sf

15

类似于什么东西

format(strptime("1970-01-01", "%Y-%m-%d", tz="UTC") + round(as.numeric(your.time)/900)*900,"%H:%M")

能够运行


14

虽然这是一个旧问题,但我想指出现在使用lubridate包很容易地处理这个问题,只需使用floor_date函数将一组POSIXct对象切割成15分钟的间隔。

做法如下:x <- lubridate::floor_date(x, "15 minutes")

编辑:用户@user3297928指出,要将时间舍入到最近的15分钟,请使用lubridate::round_date(x, "15 minutes")。上面的代码是向下取整。


5
您可以使用xts包中的align.time函数来处理四舍五入,然后使用format函数返回一个"HH:MM"字符串:
R> library(xts)
R> p <- as.POSIXct("2012-05-30 20:41:21", tz="UTC")
R> a <- align.time(p, n=60*15)  # n is in seconds
R> format(a, "%H:%M")
[1] "20:45"

5
这很优雅,但似乎只会向上舍入。 - Dominic
@Dominic:你完全正确。align.time只会向上舍入,而你想要四舍五入到最近的15分钟。抱歉。 - Joshua Ulrich
向下取整:align.time(p - lubridate :: minutes(15), n = 60 * 15) - Nicholas Hamilton

3
使用来自data.table的IDate和ITime类以及刚开发的IPeriod类,我能够得到更具可扩展性的解决方案。
只有shhhhimhuntingrabbits和PLapointe在最近一次回答中提到了nearest。xts的解决方案仅使用ceiling进行四舍五入,而我的IPeriod解决方案允许指定ceiling或floor。
为了获得最佳性能,您需要将数据保留在IDate和ITime类中。如基准测试所示,从IDate/ITime/IPeriod生成POSIXct是非常便宜的。以下是对22M时间戳的基准测试:
# install only if you don't have
install.packages(c("microbenchmarkCore","data.table"),
                 repos = c("https://olafmersmann.github.io/drat",
                           "https://jangorecki.github.io/drat/iperiod"))
library(microbenchmarkCore)
library(data.table) # iunit branch
library(xts)
Sys.setenv(TZ="UTC")

## some source data: download and unzip csv
# "http://api.bitcoincharts.com/v1/csv/btceUSD.csv.gz"
# below benchmark on btceUSD.csv.gz 11-Oct-2015 11:35 133664801

system.nanotime(dt <- fread(".btceUSD.csv"))
# Read 21931266 rows and 3 (of 3) columns from 0.878 GB file in 00:00:10
#     user   system  elapsed 
#       NA       NA 9.048991

# take the timestamp only
x = as.POSIXct(dt[[1L]], tz="UTC", origin="1970-01-01")

# functions
shhhhi <- function(your.time){
    strptime("1970-01-01", "%Y-%m-%d", tz="UTC") + round(as.numeric(your.time)/900)*900
}

PLapointe <- function(a){
    as.POSIXlt(round(as.double(a)/(15*60))*(15*60),origin=(as.POSIXlt('1970-01-01')))
}

# myRound - not vectorized

# compare results
all.equal(
    format(shhhhi(x),"%H:%M"),
    format(PLapointe(x),"%H:%M")
)
# [1] TRUE
all.equal(
    format(align.time(x, n = 60*15),"%H:%M"),
    format(periodize(x, "mins", 15),"%H:%M")
)
# [1] TRUE

# IPeriod native input are IDate and ITime - will be tested too
idt <- IDateTime(x)
idate <- idt$idate
itime <- idt$itime
microbenchmark(times = 10L,
               shhhhi(x),
               PLapointe(x),
               xts = align.time(x, 15*60),
               posix_ip_posix = as.POSIXct(periodize(x, "mins", 15), tz="UTC"),
               posix_ip = periodize(x, "mins", 15),
               ip_posix = as.POSIXct(periodize(idate, itime, "mins", 15), tz="UTC"),
               ip = periodize(idate, itime, "mins", 15))
# Unit: microseconds
#            expr         min          lq         mean       median          uq         max neval
#       shhhhi(x)  960819.810  984970.363 1127272.6812 1167512.2765 1201770.895 1243706.235    10
#    PLapointe(x) 2322929.313 2440263.122 2617210.4264 2597772.9825 2792936.774 2981499.356    10
#             xts  453409.222  525738.163  581139.6768  546300.9395  677077.650  767609.155    10
#  posix_ip_posix 3314609.993 3499220.920 3641219.0876 3586822.9150 3654548.885 4457614.174    10
#        posix_ip 3010316.462 3066736.299 3157777.2361 3133693.0655 3234307.549 3401388.800    10
#        ip_posix     335.741     380.696     513.7420     543.3425     630.020     663.385    10
#              ip      98.031     151.471     207.7404     231.8200     262.037     278.789    10

IDateITime 不仅在这个特定任务中成功扩展。同样像 IPeriod 一样,它们都是基于整数的。我认为它们在按 datetime 字段进行连接或分组时也会很好地扩展。
在线手册:https://jangorecki.github.io/drat/iperiod/


谢谢您的帖子,但是您能解释一下如何实际安装这个软件包吗?从文档中没有清楚的说明。 - shadowtalker
@ssdecontrol,请查看代码块中的第一个命令以从发布的存储库安装。否则,最可靠的方法是获取iunit分支,您可以将其添加到远程并检出分支。它基于2015年10月的data.table。 - jangorecki
啊,我错过了那一行。我本来以为你会有一个叫做“periodize”或“IPeriod”之类的单独包,而不是data.table的分支。我认为把“IDateTime”东西捆绑到data.table而不是单独的包里有点不太好。 - shadowtalker
@ssdecontrol 但是IPeriod类只是一个数字,任何包都可以使用%/%运算符处理它,将期间硬编码在那里甚至不需要存储任何属性,只是一个数字,没有黑魔法。 - jangorecki
抽象化在我看来非常有价值。 - shadowtalker
@ssdecontrol 同意,但将其提取到一个单独的包中并不是很必要,因为 data.table 已经是一个轻量级的依赖项。可以轻松地仅导入 IDateTime。请注意,计划对 IDateTime 类进行扩展以处理纳秒数据data.table#1451,这将使 IPeriod 也能处理纳秒。 - jangorecki

3
尝试使用以下代码,它结合了两个请求,并基于查看 round.POSIXt()trunc.POSIXt()的操作。
myRound <- function (x, convert = TRUE)  {
    x <- as.POSIXlt(x)
    mins <- x$min
    mult <- mins %/% 15
    remain <- mins %% 15
    if(remain > 7L || (remain == 7L && x$sec > 29))
        mult <- mult + 1
    if(mult > 3) {
        x$min <- 0
        x <- x + 3600
    } else {
        x$min <- 15 * mult
    }
    x <- trunc.POSIXt(x, units = "mins")
    if(convert) {
        x <- format(x, format = "%H:%M")
    }
    x
}

这将会产生:
> tmp <- as.POSIXct("2012-05-30 20:41:21 UTC")
> myRound(tmp)
[1] "20:45"
> myRound(tmp, convert = FALSE)
[1] "2012-05-30 20:45:00 BST"
> tmp2 <- as.POSIXct("2012-05-30 20:55:21 UTC")
> myRound(tmp2)
[1] "21:00"
> myRound(tmp2, convert = FALSE)
[1] "2012-05-30 21:00:00 BST"

这似乎没有很好地向量化,尝试使用 structure(c(1313331280, 1313334917, 1313334917, 1313340309, 1313340309, 1313340895, 1313340895, 1313341133, 1313341218, 1313341475), class = c("POSIXct", "POSIXt"), tzone = "UTC") - jangorecki

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