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

时间:2014-05-25 23:54:47

标签: r performance dplyr

en bref:

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

BUT!

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远离新的col:

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

2 个答案:

答案 0 :(得分:7)

有趣。你的基准刺激了我的兴趣。我觉得有点奇怪的是你不能直接与data.table的{​​{1}}进行比较。所以这里的结果也包含在我的系统中。

unique.data.table

比所有跑步中最快的解决方案快1.8倍。

现在,让我们将 676 中的唯一值数量增加到大约10,000,看看会发生什么。

# 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

在这里,它快了2.6倍。

  

注意:我没有在此处创建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 ,因为在实际使用案例中,您可以使用dt直接获取data.table,也可以使用fread通过引用转换setDT或直接使用data.table代替data.table(.) - 这也不是时间。


但为什么data.fame(.)dt_u都比较慢?

通过查看文件dt_ssgrouped-dt.r,这是因为1)副本和2)设置密钥。 (1)基本上是因为必须做(2)。如果您使用manip-grouped-dt.r执行汇总操作,则相当于:

dplyr

我不确定为什么在this discussion under Hadey's answer之后没有实现 ad-hoc 分组。

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..

它可以避免复制和设置密钥。


如果 mutate ,情况会更糟。它正在有效地做:

## equivalent ad-hoc by
DT[, j, by=<group_cols] ## no copy, no setkey

再次,ad-hoc解决方案就是:

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..

它避免了2个副本并设置了密钥.. only copy 是为了满足dplyr的原则,即不在原地修改对象。所以,这总是比较慢+占用DT = copy(DT) DT[, `:=`(...), by=group_cols] 内存的两倍。


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


来自dplyr的新闻项目说:

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

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


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

就本质而言,在这种特殊情况下,我能想出的最佳答案就是直接以dplyr v0.2方式致电unique.data.table

dplyr

答案 1 :(得分:0)

我遇到了这个问题并找到了一个新的解决方案here,即使用slice

我在您的数据集上运行了一些额外的基准测试,包括新的切片功能和更改dt_rn,以便它首先强制转换为data.frame

little <- expand.grid(rep(letters,121),rep(letters,121)) # my 10M row dataset.

dt_u <- function() {
    tbl_dt(little) %>% 
        group_by(Var1,Var2) %>% 
        unique(.) %>% 
        tbl_dt(.) }

dt_rn_df <- function() {
    tbl_dt(little) %>% 
        data.frame() %>%
        group_by(Var1,Var2) %>% 
        filter( row_number() == 1 ) }

dt_slice <- function() {
    tbl_dt(little) %>% 
        group_by(Var1,Var2) %>% 
        slice(1) }
dt_direct <- function() {
    unique(tbl_dt(little), by = c('Var1', 'Var2'))
}

基准测试给出:

> microbenchmark(dt_u(), dt_rn_df(), dt_slice(), dt_direct())
Unit: milliseconds
        expr      min       lq     mean   median       uq      max neval
      dt_u() 167.4490 173.1940 204.5868 194.1807 212.0271 459.0929   100
  dt_rn_df() 639.9241 648.6655 683.3479 683.9603 702.5198 810.2335   100
  dt_slice() 163.1873 167.5379 190.2340 195.3807 201.0327 314.0943   100
 dt_direct() 156.9491 159.8089 184.3647 184.4096 188.6268 326.4955   100

在您的数据集中,我发现除了强制转换为data.frame之外的所有函数大约需要相同的时间。特别是我发现使用dt_direct时Arun发现的速度没有显着提高,注意我的这个函数版本调用了unique(..., by = ...)

但是,在另一个包含160万行和28列的数据表中,我发现强制转换为data.frame实际上更快,请参阅(未提供数据):

> microbenchmark(df2_processed_f <-
+     df2_processed %>%
+     data.frame() %>%
+     dplyr::group_by(v21, v23, v26) %>%
+     filter(row_number() == 1),
+ 
+ 
+ df2_processed_dt <-
+   df2_processed %>%
+   dplyr::group_by(v21, v23, v26) %>%
+   dplyr::slice(1))
Unit: milliseconds
                                                                                                                    expr
 df2_processed_f <- df2_processed %>% data.frame() %>% dplyr::group_by(v21,      v23, v26) %>% filter(row_number() == 1)
                           df2_processed_dt <- df2_processed %>% dplyr::group_by(v21, v23,      v26) %>% dplyr::slice(1)
      min       lq     mean   median       uq      max neval
 411.7717 428.4515 491.9315 462.4099 515.8156 702.2607   100
 663.5383 676.7824 699.3258 683.2407 693.7743 933.8118   100