【问题标题】:Improve performance of data.table date+time pasting?提高 data.table 日期+时间粘贴的性能?
【发布时间】:2013-12-27 07:20:15
【问题描述】:

我不确定我可以在这里问这个问题,如果我应该在其他地方问,请告诉我。

我有一个具有这种结构的 1e6 行的 data.table:

        V1       V2     V3
1: 03/09/2011 08:05:40 1145.0
2: 03/09/2011 08:06:01 1207.3
3: 03/09/2011 08:06:17 1198.8
4: 03/09/2011 08:06:20 1158.4
5: 03/09/2011 08:06:40 1112.2
6: 03/09/2011 08:06:59 1199.3

我正在使用以下代码将 V1 和 V2 变量转换为唯一的日期时间变量:

 system.time(DT[,`:=`(index= as.POSIXct(paste(V1,V2),
                         format='%d/%m/%Y %H:%M:%S'),
                     V1=NULL,V2=NULL)])

   user  system elapsed 
   47.47    0.16   50.27 

有什么方法可以提高这种转换的性能吗?

这里是dput(head(DT))

DT <- structure(list(V1 = c("03/09/2011", "03/09/2011", "03/09/2011", 
"03/09/2011", "03/09/2011", "03/09/2011"), V2 = c("08:05:40", 
"08:06:01", "08:06:17", "08:06:20", "08:06:40", "08:06:59"), 
    V3 = c(1145, 1207.3, 1198.8, 1158.4, 1112.2, 1199.3)), .Names = c("V1", 
"V2", "V3"), class = c("data.table", "data.frame"), row.names = c(NA, 
-6L), .internal.selfref = <pointer: 0x00000000002a0788>)

【问题讨论】:

  • 如果您的日期是格林威治标准时间并且大于 1970 年 1 月 1 日,您可以尝试fasttime 包裹。
  • @aseidlitz 谢谢。我忘记了fasttime。我认为这是一个很好的候补。但是 data.table 包内部没有使用我?
  • 有一些关于向 data.table 添加 fasttime 支持的讨论,但据我所知,它仍然是 R Forge 上的一个未决请求

标签: r performance data.table posixct


【解决方案1】:

这种方法的运行速度似乎比 OP 快约 40 倍,它使用查找表并利用极快的数据表连接。此外,它还利用了这样一个事实,虽然日期和时间可能有 1e6 种组合,但最多可以有 86400 个唯一时间,甚至可能更少的日期。最后,它完全避免使用paste(...)

library(data.table)
library(stringr)

# create a dataset with 1MM rows
set.seed(1)
x  <- 1000*sample(1:1e5,1e6,replace=T)
dt <- data.table(id=1:1e6,
                 V1=format(as.POSIXct(x,origin="2011-01-01"),"%d/%m/%Y"),
                 V2=format(as.POSIXct(x,origin="2011-01-01"),"%H:%M:%S"),
                 V3=x)
DT <- dt

index.date <- function(dt) {
  # Edit: this change processes only times from the dataset; slightly more efficient
  V2 <- unique(dt$V2)
  dt.time <- data.table(char.time=V2,
                        int.time=as.integer(substr(V2,7,8))+
                          60*(as.integer(substr(V2,4,5))+
                                60*as.integer(substr(V2,1,2))))
  setkey(dt.time,char.time)
  # all dates from dataset
  dt.date <- data.table(char.date=unique(dt$V1), int.date=as.integer(as.POSIXct(unique(dt$V1),format="%d/%m/%Y")))
  setkey(dt.date,char.date)
  # join the dates
  setkey(dt,V1)
  dt <- dt[dt.date]
  # join the times
  setkey(dt,V2)
  dt <- dt[dt.time, nomatch=0]
  # numerical index
  dt[,int.index:=int.date+int.time]
  # POSIX date index
  dt[,index:=as.POSIXct(int.index,origin='1970-01-01')]
  # get back original order
  setkey(dt,id)
  return(dt)
}
# new approach
system.time(dt<-index.date(dt))
#   user  system elapsed 
#   2.26    0.00    2.26 


# original approach
DT <- dt
system.time(DT[,`:=`(index= as.POSIXct(paste(V1,V2),
                                       format='%d/%m/%Y %H:%M:%S'),
                     V1=NULL,V2=NULL)])
#   user  system elapsed 
#  84.33    0.06   84.52 

请注意,性能确实取决于有多少唯一日期。在测试用例中,有大约 1200 个唯一日期。

EDIT 建议用更多的 data.table-sugar 语法编写函数并避免使用“$”进行子集化:

index.date <- function(dt,fmt="%d/%m/%Y") {
    dt.time <- data.table(char.time = dt[,unique(V2)],key='char.time')
    dt.time[,int.time :=as.integer(substr(char.time,7,8))+
                                            60*(as.integer(substr(char.time,4,5))+
                                                        60*as.integer(substr(char.time,1,2)))]
    # all dates from dataset
    dt.date <- data.table(char.date = dt[,unique(V1)],key='char.date')
    dt.date[,int.date:=as.integer(as.POSIXct(char.date,format=fmt))]
    # join the dates
    setkey(dt,V1)
    dt <- dt[dt.date]
    # join the times
    setkey(dt,V2)
    dt <- dt[dt.time, nomatch=0]
    # numerical index
    dt[,int.index:=int.date+int.time]
    # POSIX date index
    dt[,index:=as.POSIXct.numeric(int.index,origin='1970-01-01')]
    # remove extra/temporary variables
    dt[,`:=`(int.index=NULL,int.date=NULL,int.time=NULL)]
}

【讨论】:

  • 只处理数据集中的唯一时间的小改动;效率稍高(~2.2s vs 3.9s)。
  • 谢谢。使用 data.table 连接的好主意。我稍微修改了您的解决方案,以将其放入更多 data.table 糖语法中。
  • @agstudy:干净多了,谢谢。但是为什么最后一条语句恢复了原来的顺序呢?
  • 它是一种类型(我留下了旧评论)。我改变它。我建议我们分解答案以给出一个简短的答案。你怎么看?
  • 这是否考虑闰秒?
【解决方案2】:

如果有很多时间戳会在您的数据中重复,您可以尝试添加,by=list(V1, V2),但必须有足够的重复来支付拆分成本。

这里的瓶颈是粘贴和转换,所以我认为答案是否定的。 (除非您使用另一种转换为 POSIX 的方法)

【讨论】:

  • 谢谢。我忘了提,我不应该有重复的日期(它基本上是一个时间序列数据)。
  • 此外,瓶颈实际上是转换,它比粘贴步骤花费的时间大约是 40 倍。如果您的日期是真正的 POSIX 格式(即“1970-01-01”而不是“01/01/1970”),您可以使用 Simon Urbanek 的 fasttime 库 as noted here 大大加快速度。
  • @JoshO'Brien 你对 fasttime 的看法是正确的。例如,使用 DT[,fastPOSIXct(paste(as.Date(V1,format="%d/%m/%Y"),V2))] 之类的东西至少可以将性能提高 4 倍。
猜你喜欢
  • 2021-06-13
  • 2019-09-18
  • 1970-01-01
  • 1970-01-01
  • 2018-12-02
  • 1970-01-01
  • 2021-04-22
  • 2015-09-07
  • 2013-08-23
相关资源
最近更新 更多