当前位置: 首页 > 知识库问答 >
问题:

如何按组加速子集

戚森
2023-03-14

我曾经用dplyr来实现我的数据争论,但是有些计算是“缓慢的”。特别是分组子集,我读到当有很多组时,dplyr很慢,基于这个基准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"))

我找不到按数据分组的直接方法。所以我问了这个问题:用数据按组过滤行。桌子

我们建议我使用。SD:

datas.dt[, .SD[datetime == max(datetime)], by = c("id1", "id2")]

但我有两个问题,它可以处理date,但不能处理POSIXct(“使用方法中的错误(“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 

所以我找到了另一种更快的方法来实现这一点(并用数据保持日期时间)。表:

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]

但后来我想到了数据。表将快得多,与dplyr的时间差不是很明显。

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

我是否丢失/误用了数据。桌子你有加快计算速度的想法吗?

任何帮助都将不胜感激!谢谢

编辑:关于用于微基准测试的系统和包版本的一些精确性。(电脑不是战争机器,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个答案

左丘宜然
2023-03-14

总结data.table和连接原始数据怎么样

system.time({
  datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data
  setkey(datas1, id1, id2, datetime)
  setkey(datas.dt, id1, id2, datetime)
  datas2 <- datas.dt[datas1]
})
#  user  system elapsed 
# 0.083   0.000   0.084 

可以正确过滤数据

system.time(dat1 <- datas.dt[datas.dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1])
#   user  system elapsed 
# 23.226   0.000  23.256 
all.equal(dat1, datas2)
# [1] TRUE

补遗

setkey参数是多余的,如果你使用的是Devel版本的data.table(感谢@akrun的指针)

system.time({
  datas1 <- datas.dt[, list(datetime=max(datetime)), by = c("id1", "id2")] #summarize the data
  datas2 <- datas.dt[datas1, on=c('id1', 'id2', 'datetime')]
})
甘骞尧
2023-03-14

好问题!

我将假设dfdt是易于/快速输入的对象名称。

df = datas.tbl
dt = datas.dt

-O3水平优化时进行比较:

首先,这是我的系统在当前CRAN版本的dplyr和Devel版本的data.table上的时间。开发者版本的dplyr似乎受到性能退化的困扰(Romain正在修复)。

system.time(df %>% group_by(id1, id2) %>% filter(datetime == max(datetime)))
#  25.291   0.128  25.610 

system.time(dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1])
#  17.191   0.075  17.349 

我跑了好几次,力似乎变了。但是,我使用-O3优化标志编译所有包(通过适当设置~/.R/Makevars)。我观察到数据。表的性能比我在-O3上比较过的其他软件包要好得多。

分组速度比较

其次,理解如此缓慢的原因很重要。首先,让我们将时间与组进行比较。

system.time(group_by(df, id1, id2))
#   0.303   0.007   0.311 
system.time(data.table:::forderv(dt, by = c("id1", "id2"), retGrp = TRUE))
#   0.002   0.000   0.002 

尽管总共有250000行,但您的数据大小约为38MB。在这种规模下,分组速度不太可能有明显差异。

数据。表的分组为

为什么慢?

那么原因是什么呢?让我们打开datatable.verbose选项并再次检查:

options(datatable.verbose = TRUE)
dt[dt[, .I[datetime == max(datetime)], by = c("id1", "id2")]$V1]
# Detected that j uses these columns: datetime 
# Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0
# lapply optimization is on, j unchanged as '.I[datetime == max(datetime)]'
# GForce is on, left j unchanged
# Old mean optimization is on, left j unchanged.
# Starting dogroups ... 
#   memcpy contiguous groups took 0.097s for 230000 groups
#   eval(j) took 17.129s for 230000 calls
# done dogroups in 17.597 secs

因此,eval(j)一项花费了约97%的时间!我们在j中提供的表达式针对每组进行评估。由于您有230000个组,并且eval()调用有一个惩罚,这就加起来了。

避免ava()惩罚

既然我们意识到了这种惩罚,我们已经开始实现一些常用函数的内部版本:sum均值minmax。这将/应该扩展到尽可能多的其他功能(当我们找到时间时)。

所以,让我们先计算一下获取max(datetime)的时间:

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)]
# Detected that j uses these columns: datetime 
# Finding groups (bysameorder=TRUE) ... done in 0.002secs. bysameorder=TRUE and o__ is length 0
# lapply optimization is on, j unchanged as 'list(max(datetime))'
# GForce optimized j to 'list(gmax(datetime))'

它是即时的。为什么啊?因为max()在内部被优化为gmax(),并且没有对每个230K组的ava()调用。

那么为什么datetime==max(datetime)不是即时的呢?因为解析这些表达式并在内部进行优化更加复杂,我们还没有做到这一点。

工作区

既然我们已经知道了这个问题,并且找到了一种绕过它的方法,让我们使用它。

dt.agg = dt[, .(datetime = max(datetime)), by = .(id1, id2)]
dt[dt.agg, on = c("id1", "id2", "datetime")] # v1.9.5+

这在我的Mac上需要大约0.14秒。

请注意,这仅仅是因为表达式被优化为gmax()。将其与:

dt[, .(datetime = base::max(datetime)), by = .(id1, id2)]

我同意优化更复杂的表达式以避免ava()惩罚将是理想的解决方案,但我们还没有做到这一点。

 类似资料:
  • 问题内容: 我希望在我的应用程序中有一个按钮,当按下该按钮时,将弹出此按钮(请参见下图)。我怎么做?我不想创建一个自定义共享活动,我只想要默认一个?我使用什么代码?在线上的所有教程都在Objective- C中。请尽快给出答案。 图片:http: //9to5mac.com/2014/06/30/hands-on-1password-beta-shows-off- ios-8s-touch-id-

  • 我已经构建了以下使用google的place api获取电话号码的小程序,但速度非常慢。当我测试6个项目时,它需要4.86秒到1.99秒,我不知道为什么时间会发生重大变化。我对API非常陌生,所以我甚至不确定哪些事情可以/不能加快,哪些事情留给为API提供服务的Web服务器处理,以及我自己可以改变什么。

  • 我正在开发一个Android游戏,使用Eclipse中的libGDX。我想加快我的场景(跑步机),每当一个球员得了10分。跑步机的初始速度等于0,每10分累加5。当角色掉出屏幕时,玩家就输掉了游戏。有人知道怎么做吗?

  • 问题内容: 我是编程的初学者,并开始学习Swift,以制作一款有趣的钢琴应用。 按下按钮时我无法播放声音。我已经搜索了一些网站,但是我还是个初学者,无法理解… http://www.tmroyal.com/playing-sounds-in-swift-avaudioplayer.html http://www.rockhoppertech.com/blog/swift- avfoundation

  • 我有一个数据框,其中包含各种建筑的消费数据。这些建筑被划分为几个类别,并进一步划分为子类别。如何返回每个子类别本身的建筑数量计数?