dplyr中的唯一行:rowbumber()从tbl_dt与tbl_df不一致

npj*_*pjc 8 performance r dplyr

en bref:

我想知道如何从工作流data.table的某个地方获取唯一的行dplyr.从v0.2开始我可以使用row_number==1(参见: 使用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)
Run Code Online (Sandbox Code Playgroud)

结果:

> Error in rank(x, ties.method = "first") : 
> argument "x" is missing, with no default
Run Code Online (Sandbox Code Playgroud)

这就是我实际上发现它被打破的方式.我在问:

这样或那样?

我可以使用这个unique.data.table方法:

 dt_u <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           unique(.) %>% 
           tbl_dt(.) }
Run Code Online (Sandbox Code Playgroud)

我可以用summarise那么select远的山坳新:

dt_ss <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           summarise( n = n() ) %>% 
           select( -(n) ) }
Run Code Online (Sandbox Code Playgroud)

我可以使用row_number() == 1 #DOES NOT WORK for tbl_dt!

 dt_rn <- function() {
           tbl_dt(little) %>% 
           group_by(Var1,Var2) %>% 
           filter( row_number() == 1 ) }
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

Aru*_*run 7

有趣.你的基准刺激了我的兴趣.我觉得有点奇怪,你不与之比较的data.tableunique.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
Run Code Online (Sandbox Code Playgroud)

它比所有运行中最快的解决方案快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
Run Code Online (Sandbox Code Playgroud)

而且,它的速度提高了2.6倍.

注意:我没有时间在dt这里创建,因为在实际使用情况下,您既可以使用fread直接获取data.table,也可以使用setDT转换data.table引用或直接使用data.table(.)而不是data.fame(.)- 也不是时间.


但是,为什么都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..
Run Code Online (Sandbox Code Playgroud)

在Hadey回答这个讨论之后,我不确定为什么没有实施临时分组.

## equivalent ad-hoc by
DT[, j, by=<group_cols] ## no copy, no setkey
Run Code Online (Sandbox Code Playgroud)

它避免了副本和设置键.


如果你变异,那就更糟了.它有效地做了:

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..
Run Code Online (Sandbox Code Playgroud)

在这里,ad-hoc解决方案简单地说:

DT   = copy(DT)
DT[, `:=`(...), by=group_cols]
Run Code Online (Sandbox Code Playgroud)

它避免了2个副本和设置密钥.. 唯一的副本是为了满足dplyr的原则,即不在原地修改对象.所以,这总是比较慢+占用两倍的内存dplyr.


同样,我在这里评论过,可以避免某些联接的副本.


NEWS项目dplyr v0.2说:

  • dplyr在设置数据表的键时更加小心,因此它不会意外地修改它不拥有的对象.它还避免了不必要的密钥设置,从而对性能产生负面影响.(#193,#255).

但显然有一些讨论的案例没有成功.


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

在这个本质中,在这种特殊情况下,我能想出的最佳答案就是unique.data.table直接以dplyrish方式调用:

tbl_dt(little) %>% unique(.)
Run Code Online (Sandbox Code Playgroud)