如何按组加速子集

2024-04-18

我曾经使用 dplyr 来实现数据整理,但有些计算速度“慢”。特别是按组的子集,我读到当有很多组并且基于时 dplyr 很慢这个基准 https://github.com/Rdatatable/data.table/wiki/Benchmarks-%3A-Groupingdata.table 可能会更快,所以我开始学习 data.table。

以下是如何重现接近我的真实数据(包含 25 万行和大约 23 万组)的方法。我想按 id1、id2 进行分组,并使用max(datetime)对于每个组。

Datas

# 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 按组进行子集化的直接方法,所以我问了这个问题:使用 data.table 按组过滤行 https://stackoverflow.com/questions/31827551/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')”)类的对象,并且这非常慢。例如对于 Dates :

> 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 的时间差异并不显着。

微基准测试

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)

System

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’

好问题!

我假设df and dt作为易于/快速输入的对象名称。

df = datas.tbl
dt = datas.dt

比较于-O3等级优化:

首先,这是我的系统在当前 CRAN 版本上的计时dplyr和开发版本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适当地)。我观察到data.table性能比我比较过的其他软件包要好得多-O3.

分组速度对比

其次,了解这种缓慢的原因很重要。首先我们将时间与group.

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 

尽管总共有 250,000 行,但数据大小约为 38MB 左右。在这种大小下,分组速度不太可能出现明显差异。

data.table的分组是>100x这里更快,这显然不是这么慢的原因......

为什么慢?

那么原因是什么呢?让我们开启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

So eval(j)仅此一项就花费了约 97% 的时间!我们提供的表达式j被评估为每组。由于您有 230,000 个组,并且对组有惩罚eval()打电话,加起来。

避免eval() penalty

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

那么,让我们尝试计算一下获取的时间max(datetime) first:

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()并且没有eval()调用 230K 个组中的每个组。

那么为什么不是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 秒。

请注意,这只是快速because表达式被优化为gmax()。将其与以下内容进行比较:

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何按组加速子集 的相关文章

  • 获取包含矩阵行内最大值的列名称,该矩阵在数组内包含单独的最大值

    例如给出 dim1 lt c P PO C T dim2 lt c LL RR R Y dim3 lt c Jerry1 Jerry2 Jerry3 Q lt array 1 48 c 4 4 3 dimnames list dim1 di
  • 在闪亮的数据表中为每个单元格显示工具提示或弹出窗口?

    有没有什么方法可以为 r闪亮数据表中的每个单元格获取工具提示 有很多方法可以获取悬停行或列 但我找不到一种方法来获取行和列索引并为每个单元格显示不同的悬停工具提示 任何人都可以修改以下代码吗 library shiny library DT
  • 是否有weighted.median()函数?

    我正在寻找类似形式的东西weighted mean 我通过搜索找到了一些解决方案 这些解决方案写出了整个函数 但希望有一些更用户友好的解决方案 以下软件包都有计算加权中位数的函数 aroma light isotone limma cwhm
  • R:改变堆积条形图的颜色

    library ggplot2 df2 lt data frame supp rep c VC OJ each 3 dose rep c D0 5 D1 D2 2 len c 6 8 15 33 4 2 10 29 5 head df2 g
  • R 中的数据框操作 - 将单元格向左移动并删除 NA

    我有一个数据框 其列由随机分布的值和 NA 组成 如下所示 a lt c S E NA S NA b lt c A NA M G K c lt c I NA NA NA L meh lt dataframe a b c 1 2 3 4 5
  • 如何根据多个条件创建列?

    我有一个数据框 我想根据多个条件创建一个列 v1 v2 v3 v4 v5 4 1 2 4 5 5 5 2 4 5 6 21 9 20 30 50 6 4 5 7 9 10 3 6 5 9 基本上 使用以下可能的值创建 v6 Cat dog
  • 是否有 java.lang.String 的内存高效替代品?

    看完之后这篇旧文章 http www javaworld com javaworld javatips jw javatip130 html page 2测量几种对象类型的内存消耗 我惊讶地发现有多少内存String在Java中的使用 le
  • R:根据列名部分匹配计算行平均值

    我有一个看起来像这样的表 er er 1 as as 1 as 2 rt op a 1 6 90 8 6 4 87 b 1 8 56 7 5 5 9 c 8 7 6 4 5 9 6 d 1 0 8 6 4 3 6 e 9 7 2 4 3 8
  • 使用非标准评估公式

    我正在创建一个使用的包非标准评价 http adv r had co nz Computing on the language html跟踪列的含义 该包在函数之间传递数据框 这些函数执行同一组列的各种操作 非标准评估对此非常有用 my s
  • 上传到 Shiny 服务器后在 R 中解压文件

    我正在尝试在我们的本地服务器上使用 Shiny 来构建一个应用程序 允许用户上传包含 ESRI shapefile 和关联文件的 zip 文件 Shiny 服务器的 fileInput 可以获取数据 并且当它这样做时 它会将其存储在临时目录
  • 使用操作按钮在闪亮的 R 中添加包含现有数据框的新行

    我正在构建一个闪亮的表单 它将从 textInput 字段获取数据 并将这些输入与文本文件 将通过文件输入上传 组合起来 并在主面板中显示输出 有一个操作按钮用于第一次更新数据 从文本输入中获取数据并与处理后的文本文件合并 我添加了另一个操
  • 将文件名附加到 R 中的数据框

    我想将文件名附加到我的表中 但它似乎并没有真正起作用 我正在做的是迭代文件名列表 打开它们 将所有数据附加到一个数据帧 对于每个附加文件 我想添加其文件名 我希望将其附加到每一行 以便稍后当我查看数据时 我会知道给定行源自哪个文件 但似乎并
  • rPlot 工具提示问题

    我有一个使用 rCharts 工具提示的简单示例 但似乎不起作用 set seed 1 test lt data frame x rnorm 100 y rnorm 100 rPlot y x data test type point to
  • 文件错误(文件,“rt”):complete.cases 程序中的“描述”参数无效

    我正在编写一个 R 函数 该函数读取充满文件的目录并报告每个数据文件中完全观察到的案例的数量 该函数返回一个数据框 其中第一列是文件名称 第二列是完整案例数 such as id nobs 1 108 2 345 etc 这是我写的函数 c
  • 聚合函数在数据框中创建不需要的向量

    我在函数中创建数据帧时遇到了一个奇怪的问题 但是 在 data frame 之外使用相同的方法效果很好 这是基本函数 我用它来计算数据集的平均值 标准差和标准误差 aggregateX lt function formula dataset
  • autoplot.microbenchmark 实际绘制了什么?

    根据文档 microbenchmark autoplot 使用 ggplot2 生成更清晰的微基准计时图 凉爽的 让我们尝试一下示例代码 library ggplot2 tm lt microbenchmark rchisq 100 0 r
  • 函数速度测试的奇怪结果

    我编写了一个使用递归来查找最大公因数 分母 的函数 gt gcd function a b if length a length b gt 1 warning Only scalars allowed using first element
  • IronPython 中批量求值表达式的性能

    在 C 4 0 应用程序中 我有一个具有相同长度的强类型 IList 的字典 一个基于动态强类型列的表 我希望用户根据将在所有行上聚合的可用列提供一个或多个 python 表达式 在静态上下文中它将是 IDictionary
  • 如何对单个 TypoSript 对象生成进行基准测试?

    我想对单个 TypoScript 对象生成进行基准测试以控制性能 是否可以使用某些 stdWrap 方法 我想要对其进行基准测试的 TS 对象示例 Test 1 page 10 RECORDS page 10 tables pages so
  • 根据列中的部分字符串匹配选择数据框行

    我想根据列中字符串的部分匹配从数据框中选择行 例如列 x 包含字符串 hsa 使用sqldf if它有一个like语法 我会做类似的事情 select from lt gt where x like hsa 很遗憾 sqldf不支持该语法

随机推荐