r - dplyr : row_number() from tbl_dt inconsistent to tbl_df 中的唯一行

标签 r performance dplyr

英文缩写:

我想知道如何从 data.table 中获取唯一的行在某处沿着 dplyr工作流程。从 v0.2 开始,我可以使用 row_number==1 (见:Remove duplicated rows using dplyr)

但!
tbl_df(data) %>% group_by(Var1,Var2) %>% filter(row_number() == 1)作品。
tbl_dt(data) %>% group_by(Var1,Var2) %>% filter(row_number() == 1)没有。 这是一个错误吗?

设置:

library(dplyr)
library(data.table)
library(microbenchmark)

little <- expand.grid(rep(letters,121),rep(letters,121)) # my 10M row dataset.
tbl_dt(little) %>% group_by(Var1,Var2) %>% filter(row_number() == 1)

结果:
> Error in rank(x, ties.method = "first") : 
> argument "x" is missing, with no default

这就是我实际上发现它坏了的方式。我是问:

这样还是那样?

我可以使用 unique.data.table方法:
 dt_u <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           unique(.) %>% 
           tbl_dt(.) }

我可以用 summarise然后 select离开新上校:
dt_ss <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           summarise( n = n() ) %>% 
           select( -(n) ) }

我可以用 row_number() == 1 # 不适用于 tbl_dt!
 dt_rn <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           filter( row_number() == 1 ) }

以此类推 tbl_df()等价物。

对等价的 data.table/data.frame 方法进行基准测试 microbenchmark(...,times=20) :
> Unit: milliseconds
>     expr       min        lq    median        uq       max neval
>  dt_ss()  579.0385  618.0002  661.9056  694.0705  764.2221    20
>  dt_u()   690.1284  729.8723  756.5505  783.7379  897.4799    20
>  df_ss()  419.7841  436.9871  448.1717  461.7023  523.2798    20
>  df_u()  3971.1699 4044.3663 4097.9848 4168.3468 4245.8346    20
>  df_rn()  646.1497  687.3472  711.3924  724.6235  754.3166    20

最佳答案

有趣的。你的基准测试激起了我的兴趣。我觉得你不和 data.table 比较有点奇怪的 unique.data.table直接地。因此,这是将其也包含在我的系统中的结果。

# extra function with which the benchmark shown below was run
dt_direct <- function() unique(dt) # where dt = as.data.table(little)

# Unit: milliseconds
#         expr       min        lq    median        uq       max neval
#       dt_u() 1472.2460 1571.0871 1664.0476 1742.5184 2647.2118    20
#       df_u() 6084.2877 6303.9058 6490.1686 6844.8767 7370.3322    20
#      dt_ss() 1340.8479 1485.4064 1552.8756 1586.6706 1810.2979    20
#      df_ss()  799.5289  835.8599  884.6501  957.2208 1251.5994    20
#      df_rn() 1410.0145 1576.2033 1660.1124 1770.2645 2442.7578    20
#  dt_direct()  452.6010  463.6116  486.5015  568.0451  670.3673    20

它比所有运行中最快的解决方案快 1.8 倍。

现在,让我们将唯一值的数量从 676 增加到大约 10,000,看看会发生什么。
val = paste0("V", 1:100)
little <- data.frame(Var1=sample(val, 1e7, TRUE), Var2=sample(val, 1e7, TRUE))
dt <- as.data.table(little)

# Unit: milliseconds
#         expr      min        lq    median        uq       max neval
#       dt_u() 1709.458 1776.3510 1892.7761 1991.6339 2562.9171    20
#       df_u() 7541.364 7735.4725 7981.3483 8462.9093 9552.8629    20
#      dt_ss() 1555.110 1627.6519 1791.5219 1911.3594 2299.2864    20
#      df_ss() 1436.355 1500.1043 1528.1319 1649.3043 1961.9945    20
#      df_rn() 2001.396 2189.5164 2393.8861 2550.2198 3047.7019    20
#  dt_direct()  508.596  525.7299  577.6982  674.2288  893.2116    20

在这里,速度提高了 2.6 倍。

Note: I don't time the creation of dt here because, in real use cases, you can either use fread to get a data.table directly, or use setDT to convert a data.table by reference or directly use data.table(.) instead of data.fame(.) - which is not timed as well.



但为什么两者都是dt_udt_ss那么慢吗?

通过查看文件 grouped-dt.rmanip-grouped-dt.r ,这是由于 1) 副本和 2) 设置键而发生的。 (1)基本上是因为不得不做(2)。如果您使用 dplyr 进行汇总操作,它等价于:
DT <- copy(DT);
setkey(DT, <group_cols>  ## these two are in grouped_dt
DT[, j, by=<group_cols>] ## this is in summarise.grouped_dt
DT <- copy(DT)           ## because it calls grouped_dt AGAIN!
## and sets key again - which is O(n) now as DT checked if sorted first..

我不确定为什么在 this discussion under Hadey's answer 之后没有实现临时分组.
## equivalent ad-hoc by
DT[, j, by=<group_cols] ## no copy, no setkey

它避免了副本和设置 key 。

如果你发生变异,那就更糟了。它有效地做:
DT <- copy(DT)
setkey(DT, <group_cols>) ## these two are in grouped_dt
DT <- copy(DT)           ## mutate.grouped_dt copies copied data again
DT[, `:=`(...), by=<group_cols>] ## this is in mutate.grouped_dt
DT = copy(DT) ## because of another call to grouped_dt!!!
## and sets key again - which is O(n) now as DT is checked if sorted first..

同样,临时解决方案很简单:
DT   = copy(DT)
DT[, `:=`(...), by=group_cols]

它避免了 2 个副本和设置 key 。唯一的副本是为了满足 dplyr 不就地修改对象的理念。所以,这总是会更慢 + 占用 dplyr 中两倍的内存.

同样,可以避免某些连接上的副本 as I've commented here .

来自 dplyr v0.2 的新闻项目说:

  • dplyr is more careful when setting the keys of data tables, so it never accidentally modifies an object that it doesn't own. It also avoids unnecessary key setting which negatively affected performance. (#193, #255).


但很明显,相当多的讨论案例没有成功。

到目前为止,我在你的问题下写了关于性能标签的文章。也就是说,如果您正在寻找性能,则应该避免所有会生成(不必要的)副本(和设置 key )的情况,直到修复为止。

从本质上讲,在这种特殊情况下,我能想出的最佳答案就是调用 unique.data.table直接在 dplyr方式:
tbl_dt(little) %>% unique(.)

关于r - dplyr : row_number() from tbl_dt inconsistent to tbl_df 中的唯一行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23861047/

相关文章:

r - 如何使用其他数据添加线条到ggplot?

c# - 为什么 LINQ to objects 方法的顺序很重要

r - 根据 row_number() 过滤 data.frame

r - mutate 和 rowSums 排除列

java - 像 Hibernate 这样的框架带来了多少开销?

r - 查看每个 id 是否有任何非 NA 值

r - 绘制常规二维图,但添加第三维作为热图

r - 垂直组合多个条形图

带保留评估的 RMOA Hoeffding 树

java - 减少来自 Java Lambda 的 DynamoDB 延迟