【问题标题】:Fastest method to replace data values conditionally in data.table (speed comparison)在data.table中有条件地替换数据值的最快方法(速度比较)
【发布时间】:2015-08-13 13:03:49
【问题描述】:

为什么第二种方法会因为增加data.table大小而变慢:

library(data.table)
DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)

1:

DF1=DF2=DF

system.time(DF[y==6,"y"]<-10)
user  system elapsed 
2.793   0.699   3.497 

2:

system.time(DF1$y[DF1$y==6]<-10)
user  system elapsed 
6.525   1.555   8.107 

3:

system.time(DF2[y==6, y := 10]) # slowest!
user  system elapsed 
7.925   0.626   8.569 

>sessionInfo()
R version 3.2.1 (2015-06-18)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 14.04.3 LTS

有没有更快的方法来做到这一点?

【问题讨论】:

    标签: r data.table


    【解决方案1】:

    在您的最后一个案例中,这是 data.table 中的 自动索引 功能的结果,自 v1.9.4+ 起。阅读更多内容以获取完整图片:-)。

    当您执行DT[col == .]DT[col %in% .] 时,第一次运行时会自动生成一个索引。索引就是您指定的列的order。索引的计算非常快(使用计数排序/真正的基数排序)。

    该表有 1.2 亿行,大约需要:

    # clean session
    require(data.table)
    set.seed(1L)
    DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)
    
    system.time(data.table:::forderv(DF, "y"))
    #   3.923   0.736   4.712 
    

    旁注:y 不必真的是 double (排序需要更长的时间)。如果我们将其转换为整数类型:

       DF[, y := as.integer(y)]
       system.time(data.table:::forderv(DF, "y"))
       #    user  system elapsed 
       #   0.569   0.140   0.717 
    

    优点是该列上使用==%in% 的任何后续子集都将非常快速(SlidesR scriptvideo,来自 Matt 的演示文稿)。例如:

    # clean session, copy/paste code from above to create DF
    system.time(DF[y==6, y := 10])
    #    user  system elapsed 
    #   4.750   1.121   5.932 
    
    system.time(DF[y==6, y := 10])
    #    user  system elapsed 
    #   4.002   0.907   4.969 
    

    哦,等一下……它并不快。但是..索引..?!?我们每次都用新值替换同一列。这会导致该列的顺序发生变化(从而删除索引)。让我们子集y,但修改v

    # clean session
    require(data.table)
    set.seed(1L)
    DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)
    
    system.time(DF[y==6, v := 10L])
    #    user  system elapsed 
    #   4.653   1.071   5.765 
    system.time(DF[y==6, v := 10L])
    #    user  system elapsed 
    #   0.685   0.213   0.910 
    
    options(datatable.verbose=TRUE)
    system.time(DF[y==6, v := 10L])
    # Using existing index 'y'
    # Starting bmerge ...done in 0 secs
    # Detected that j uses these columns: v 
    # Assigning to 40000059 row subset of 120000000 rows
    #    user  system elapsed 
    #   0.683   0.221   0.914 
    

    您可以看到计算索引的时间(使用二进制搜索)需要 0 秒。还要检查?set2key()

    如果您不打算重复进行子集化,或者像您的情况那样,对同一列进行子集化和修改,那么通过执行options(datatable.auto.index = FALSE) 禁用该功能是有意义的,归档#1264

    # clean session
    require(data.table)
    options(datatable.auto.index = FALSE) # disable auto indexing
    set.seed(1L)
    DF = data.table(x=rep(c("a","b","c"),each=40000000), y=sample(c(1,3,6),40000000,T), v=1:9)
    
    system.time(DF[y==6, v := 10L])
    #    user  system elapsed 
    #   1.067   0.274   1.367 
    system.time(DF[y==6, v := 10L])
    #    user  system elapsed 
    #   1.100   0.314   1.443 
    

    这里的区别不大。矢量扫描的时间为system.time(DF$y == 6) = 0.448s

    总而言之,在您的情况下,矢量扫描更有意义。但总的来说,这个想法是最好支付一次罚金并在该列的未来子集上获得快速结果,而不是每次都进行矢量扫描。

    自动索引功能相对较新,并且会随着时间的推移而扩展,并且可能会进行优化(也许有些地方我们还没有研究过)。在回答这个问题时,我意识到我们没有显示计算排序顺序的时间(使用fsort(),我猜在那里花费的时间可能是时间非常接近的原因,提交#1265)。


    关于你的第二种情况很慢,不太清楚为什么。我怀疑这可能是由于 R 部分不必要的副本。你用的是什么版本的R?对于未来,请始终发布您的sessionInfo() 输出。

    【讨论】:

    • @DavidArenburg,矢量扫描/二分搜索的时间没有改变(这是耗时的部分)。
    • 我想知道您能否解释一下为什么 DF[y==6,"y"]&lt;-10 有效,而 DF[y==6,"y"] 实际上什么也没做。这让我大吃一惊。
    • @DavidArenburg,检查data.table:::`[&lt;-.data.table`。我也从未使用过它。我认为没有人使用它。为了向后兼容,它必须仍然存在,不确定。我的猜测是,该功能也没有发生任何和所有改进(因为它不是惯用的)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-20
    • 2016-01-07
    • 2014-08-23
    • 1970-01-01
    • 2011-11-06
    相关资源
    最近更新 更多