我曾经用dplyr实现我的数据争论,但有些计算是"slow" . 特别是按组子集,我读到dplyr很慢,当有很多组并基于this benchmark data.table可能会更快,所以我开始学习data.table .
以下是如何使用250k行和大约230k组重现与我的实际数据相近的内容 . 我想按id1,id2进行分组,并为每个组分配 max(datetime)
的行 .
数据
# random datetime generation function by Dirk Eddelbuettel
# https://stackoverflow.com/questions/14720983/efficiently-generate-a-random-sample-of-times-and-dates-between-two-dates
rand.datetime <- function(N, st = "2012/01/01", et = "2015/08/05") {
st <- as.POSIXct(as.Date(st))
et <- as.POSIXct(as.Date(et))
dt <- as.numeric(difftime(et,st,unit="sec"))
ev <- sort(runif(N, 0, dt))
rt <- st + ev
}
set.seed(42)
# Creating 230000 ids couples
ids <- data.frame(id1 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]"),
id2 = stringi::stri_rand_strings(23e4, 9, pattern = "[0-9]"))
# Repeating randomly the ids[1:2000, ] to create groups
ids <- rbind(ids, ids[sample(1:2000, 20000, replace = TRUE), ])
# Adding random datetime variable and dummy variables to reproduce real datas
datas <- transform(ids,
datetime = rand.datetime(25e4),
var1 = sample(LETTERS[1:6], 25e4, rep = TRUE),
var2 = sample(c(1:10, NA), 25e4, rep = TRUE),
var3 = sample(c(1:10, NA), 25e4, rep = TRUE),
var4 = rand.datetime(25e4),
var5 = rand.datetime(25e4))
datas.tbl <- tbl_df(datas)
datas.dt <- data.table(datas, key = c("id1", "id2"))
我找不到通过data.table分组的直接方式,所以我问了这个问题:Filter rows by groups with data.table
我们建议我使用.SD:
datas.dt[, .SD[datetime == max(datetime)], by = c("id1", "id2")]
但我有两个问题,它适用于日期但不适用于POSIXct(“UseMethod中的错误(”as.data.table“):没有适用于”as.data.table“的方法应用于类”c“的对象POSIXct','POSIXt')“”),这非常慢 . 比如日期:
> system.time({
+ datas.dt[, .SD[as.Date(datetime) == max(as.Date(datetime))], by = c("id1", "id2")]
+ })
utilisateur système écoulé
207.03 0.00 207.48
所以我发现使用data.table实现这一目标(以及保持日期时间)要快得多:
功能
f.dplyr <- function(x) x %>% group_by(id1, id2) %>% filter(datetime == max(datetime))
f.dt.i <- function(x) x[x[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]
f.dt <- function(x) x[x[, datetime == max(datetime), by = c("id1", "id2")]$V1]
但后来我认为data.table会快得多,与dplyr的时差也没有意义 .
Microbenchmark
mbm <- microbenchmark(
dplyr = res1 <- f.dplyr(datas.tbl),
data.table.I = res2 <- f.dt.i(datas.dt),
data.table = res3 <- f.dt(datas.dt),
times = 50L)
Unit: seconds
expr min lq mean median uq max neval
dplyr 31.84249 32.24055 32.59046 32.61311 32.88703 33.54226 50
data.table.I 30.02831 30.94621 31.19660 31.17820 31.42888 32.16521 50
data.table 30.28923 30.84212 31.09749 31.04851 31.40432 31.96351 50
我是否遗漏/滥用data.table?你有想加快这个计算吗?
任何帮助将非常感谢!谢谢
编辑:有关用于微基准测试的系统和软件包版本的一些精确性 . (电脑不是战争机器,12Go i5)
系统
sessionInfo()
R version 3.1.3 (2015-03-09)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
locale:
[1] LC_COLLATE=French_France.1252 LC_CTYPE=French_France.1252
[3] LC_MONETARY=French_France.1252 LC_NUMERIC=C
[5] LC_TIME=French_France.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] readr_0.1.0 ggplot2_1.0.1 microbenchmark_1.4-2
[4] data.table_1.9.4 dplyr_0.4.1 plyr_1.8.2
loaded via a namespace (and not attached):
[1] assertthat_0.1 chron_2.3-45 colorspace_1.2-6 DBI_0.3.1
[5] digest_0.6.8 grid_3.1.3 gtable_0.1.2 lazyeval_0.1.10
[9] magrittr_1.5 MASS_7.3-39 munsell_0.4.2 parallel_3.1.3
[13] proto_0.3-10 Rcpp_0.11.5 reshape2_1.4.1 scales_0.2.4
[17] stringi_0.4-1 stringr_0.6.2 tools_3.1.3
> packageVersion("data.table")
[1] ‘1.9.4’
> packageVersion("dplyr")
[1] ‘0.4.1’
2 回答
好问题!
我假设
df
和dt
是易于/快速输入的对象名称 .Comparison at -O3 level optimisation:
首先,这是我的系统在
dplyr
的当前CRAN版本和data.table
的开发版本上的时间 .dplyr
的devel版本似乎遭受了性能回归(并且正由Romain修复) .我跑了好几次,而且似乎改变了 . 但是,我使用
-O3
优化标志编译所有包(通过适当地设置~/.R/Makevars
) . 而且我观察到data.table
性能比我在-O3
上比较的其他软件包要好得多 .Grouping speed comparison
其次,了解这种缓慢的原因很重要 . 首先让我们将时间与组合进行比较 .
即使总共有250,000行,您的数据大小约为38MB . 在这个尺寸下,分组速度不太可能出现显着差异 .
data.table
的分组在这里更快>100x
,显然不是这种缓慢的原因......Why is it slow?
那么's the reason? Let' s打开
datatable.verbose
选项并再次检查:所以
eval(j)
单独占用了~97%的时间!我们在j
中提供的表达式针对每个组进行了评估 . 既然你eval()
对eval()
的惩罚是一个惩罚,那就加起来了 .Avoiding the eval() penalty
由于我们已经开始实施并开始实现一些常用函数的内部版本:
sum
,mean
,min
,max
. 这将/应该扩展到尽可能多的其他功能(当我们找到时间) .所以,让我们先尝试计算一下获取
max(datetime)
的时间:而且它是即时的 . 为什么?因为
max()
内部优化为gmax()
,并且每个230K组都没有eval()
调用 .那么为什么不是
datetime == max(datetime)
瞬间?因为解析这些表达式并在内部进行优化会更复杂,而我们还没有完成它 .Workaround
所以现在我们知道了这个问题,以及一种绕过它的方法,让我们使用它 .
我的Mac需要大约0.14秒 .
请注意,这只是快速的,因为表达式被优化为
gmax()
. 比较它:我同意优化更复杂的表达式以避免
eval()
惩罚是理想的解决方案,但我们还没有 .如何总结data.table和
join
原始数据这正确地过滤了数据
Addendum
setkey
参数是多余的,如果你使用data.table
的devel version(感谢@akrun指针)