R 中大规模时间序列操作的性能属性(主要是 xts 和 data.table)
Performance properties of time-series operations in R at scale (mainly xts and data.table)
我正在进行一个包含大型时间序列数据集的新项目,从中将相关计算输入 shiny
应用程序。因此,我对效率很感兴趣。这些操作通常仅限于基本周期转换和风险指标的后续汇总统计。
我正在研究用哪个 library/approach 来构建计算脚本。目前,我可以接受 xts
和 data.table
。虽然我可以求助于 quantmod
和 TTR
库,但我对在生产中部署黑盒函数犹豫不决,更愿意保持完全的可追溯性。
到目前为止,我已经执行了以下基准测试练习,其中 data.frame
的每日价格转换为每月 returns。到目前为止使用的包是 xts
、data.table
和 quantmod
(作为参考)。代码粘贴在下面,但也可以在 GitHub.
上找到
基准代码
# Simple return exercise: Daily Prices to Monthly Returns
# Input: Nx2 data.frame with columns (N days, price)
# Output: Mx2 object with columns (M months, return)
# Three different functions: 1. xts, 2. data.table, 3. quantmod
rm(list = ls()); gc()
library(data.table)
library(zoo)
library(xts)
library(ggplot2)
library(quantmod)
# Asset params
spot = 100
r = 0.01
sigma = 0.02
N = 1e5
# Input data: Nx2 data.frame (date, price)
pmat = data.frame(
date = seq.Date(as.Date('1970-01-01'), by = 1, length.out = N),
price = spot * exp(cumsum((r - 0.5 * sigma**2) * 1/N + (sigma * (sqrt(1/N)) * rnorm(N, mean = 0, sd = 1))))
)
# Output functions
# 1. xts standalone
xtsfun = function(mat){
xtsdf = as.xts(mat[, 2], order.by = mat[, 1])
eom_prices = to.monthly(xtsdf)[, 4]
mret = eom_prices/lag.xts(eom_prices) - 1; mret[1] = eom_prices[1]/xtsdf[1] - 1
mret
}
# 2. data.table standalone
dtfun = function(mat){
dt = setNames(as.data.table(mat), c('V1', 'V2'))
dt[, .(EOM = last(V2)), .(Month = as.yearmon(V1))][, .(Month, Return = EOM/shift(EOM, fill = first(mat[, 2])) - 1)]
}
# 3. quantmod (black box library)
qmfun = function(mat){
qmdf = as.xts(mat[, 2], order.by = mat[, 1])
monthlyReturn(qmdf)
}
# Check 1 == 2 == 3:
all.equal(
unlist(dtfun(pmat[1:1000,])[, Return]),
as.numeric(xtsfun(pmat[1:1000,])),
as.numeric(qmfun(pmat[1:1000,])),
scale = NULL
)
# Benchmark
library(microbenchmark)
gc()
mbm = microbenchmark(
xts = xtsfun(pmat),
data.table = dtfun(pmat),
quantmod = qmfun(pmat),
times = 50
)
mbm
结果
对于N = 1e5
,三种方法的表现相似:
Unit: milliseconds
expr min lq mean median uq max neval
xts 20.62520 22.93372 25.14445 23.84235 27.25468 39.29402 50
data.table 21.23984 22.29121 27.28266 24.05491 26.25416 98.35812 50
quantmod 14.21228 16.71663 19.54709 17.19368 19.38106 102.56189 50
然而,对于 N = 1e6
,我观察到 data.table
的显着性能差异:
Unit: milliseconds
expr min lq mean median uq max neval
xts 296.8969 380.7494 408.7696 397.4292 431.1306 759.7227 50
data.table 1562.3613 1637.8787 1669.8513 1651.4729 1688.2312 1969.4942 50
quantmod 144.1901 244.2427 278.7676 268.4302 331.4777 418.7951 50
我很好奇是什么导致了这个结果,特别是因为 data.table
通常在总体上表现出色 N
。当然,dtfun
可能只是写得不好(我非常感谢任何代码改进),但我使用其他方法获得了类似的结果,包括 EOM 日期的自连接和每日 [=60] 的 cumprod
=].
xts
and/or quantmod
是否受益于任何内部 rcpp
或 eqv 调用以提高其性能?最后,如果您知道任何其他用于大型 TS 的竞争性独立解决方案(base
?,dplyr
?),我洗耳恭听。
答案在于 data.table
的 date
处理。本质上,它采用了相对较慢的 ISOdate
格式。当改用基于整数的 date
分组时,结果转向 data.table
.
我已经更新了TSBenchmark repository using updated solutions for xts
and data.table
. I much appreciate the improvements provided by Joshua Ulrich and Matt Dowle值得满分的人。
我正在进行一个包含大型时间序列数据集的新项目,从中将相关计算输入 shiny
应用程序。因此,我对效率很感兴趣。这些操作通常仅限于基本周期转换和风险指标的后续汇总统计。
我正在研究用哪个 library/approach 来构建计算脚本。目前,我可以接受 xts
和 data.table
。虽然我可以求助于 quantmod
和 TTR
库,但我对在生产中部署黑盒函数犹豫不决,更愿意保持完全的可追溯性。
到目前为止,我已经执行了以下基准测试练习,其中 data.frame
的每日价格转换为每月 returns。到目前为止使用的包是 xts
、data.table
和 quantmod
(作为参考)。代码粘贴在下面,但也可以在 GitHub.
基准代码
# Simple return exercise: Daily Prices to Monthly Returns
# Input: Nx2 data.frame with columns (N days, price)
# Output: Mx2 object with columns (M months, return)
# Three different functions: 1. xts, 2. data.table, 3. quantmod
rm(list = ls()); gc()
library(data.table)
library(zoo)
library(xts)
library(ggplot2)
library(quantmod)
# Asset params
spot = 100
r = 0.01
sigma = 0.02
N = 1e5
# Input data: Nx2 data.frame (date, price)
pmat = data.frame(
date = seq.Date(as.Date('1970-01-01'), by = 1, length.out = N),
price = spot * exp(cumsum((r - 0.5 * sigma**2) * 1/N + (sigma * (sqrt(1/N)) * rnorm(N, mean = 0, sd = 1))))
)
# Output functions
# 1. xts standalone
xtsfun = function(mat){
xtsdf = as.xts(mat[, 2], order.by = mat[, 1])
eom_prices = to.monthly(xtsdf)[, 4]
mret = eom_prices/lag.xts(eom_prices) - 1; mret[1] = eom_prices[1]/xtsdf[1] - 1
mret
}
# 2. data.table standalone
dtfun = function(mat){
dt = setNames(as.data.table(mat), c('V1', 'V2'))
dt[, .(EOM = last(V2)), .(Month = as.yearmon(V1))][, .(Month, Return = EOM/shift(EOM, fill = first(mat[, 2])) - 1)]
}
# 3. quantmod (black box library)
qmfun = function(mat){
qmdf = as.xts(mat[, 2], order.by = mat[, 1])
monthlyReturn(qmdf)
}
# Check 1 == 2 == 3:
all.equal(
unlist(dtfun(pmat[1:1000,])[, Return]),
as.numeric(xtsfun(pmat[1:1000,])),
as.numeric(qmfun(pmat[1:1000,])),
scale = NULL
)
# Benchmark
library(microbenchmark)
gc()
mbm = microbenchmark(
xts = xtsfun(pmat),
data.table = dtfun(pmat),
quantmod = qmfun(pmat),
times = 50
)
mbm
结果
对于N = 1e5
,三种方法的表现相似:
Unit: milliseconds
expr min lq mean median uq max neval
xts 20.62520 22.93372 25.14445 23.84235 27.25468 39.29402 50
data.table 21.23984 22.29121 27.28266 24.05491 26.25416 98.35812 50
quantmod 14.21228 16.71663 19.54709 17.19368 19.38106 102.56189 50
然而,对于 N = 1e6
,我观察到 data.table
的显着性能差异:
Unit: milliseconds
expr min lq mean median uq max neval
xts 296.8969 380.7494 408.7696 397.4292 431.1306 759.7227 50
data.table 1562.3613 1637.8787 1669.8513 1651.4729 1688.2312 1969.4942 50
quantmod 144.1901 244.2427 278.7676 268.4302 331.4777 418.7951 50
我很好奇是什么导致了这个结果,特别是因为 data.table
通常在总体上表现出色 N
。当然,dtfun
可能只是写得不好(我非常感谢任何代码改进),但我使用其他方法获得了类似的结果,包括 EOM 日期的自连接和每日 [=60] 的 cumprod
=].
xts
and/or quantmod
是否受益于任何内部 rcpp
或 eqv 调用以提高其性能?最后,如果您知道任何其他用于大型 TS 的竞争性独立解决方案(base
?,dplyr
?),我洗耳恭听。
答案在于 data.table
的 date
处理。本质上,它采用了相对较慢的 ISOdate
格式。当改用基于整数的 date
分组时,结果转向 data.table
.
我已经更新了TSBenchmark repository using updated solutions for xts
and data.table
. I much appreciate the improvements provided by Joshua Ulrich and Matt Dowle值得满分的人。