在data.table(速度比较)中有条件地替换数据值的最快方法

use*_*187 10 r data.table

为什么第二种方法会因增加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)
Run Code Online (Sandbox Code Playgroud)

1:

DF1=DF2=DF

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

2:

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

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

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

Aru*_*run 10

在你的最后一个案例中,它是自动索引功能的结果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 
Run Code Online (Sandbox Code Playgroud)

旁注:y不需要真正加倍(订购需要更长时间).如果我们将其转换为整数类型:

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

优点是该列上的任何后续子集使用==%in%将快速闪耀(幻灯片,R脚本,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 
Run Code Online (Sandbox Code Playgroud)

哦等一下......它不快.但..索引..?!?我们每次都用新值替换同一列.这导致该列的顺序发生变化(从而删除索引).让我们进行子集化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 
Run Code Online (Sandbox Code Playgroud)

您可以看到计算索引的时间(使用二进制搜索)需要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 
Run Code Online (Sandbox Code Playgroud)

这里的差异并不大.矢量扫描的时间是system.time(DF$y == 6)= 0.448s.

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

自动索引功能相对较新,并且会随着时间的推移而扩展,并且可能已经过优化(可能还有一些地方我们没有看过).在回答这个问题时,我意识到我们没有显示计算排序顺序的时间(使用fsort(),我猜测时间花费的时间可能非常接近,提交#1265).


至于你的第二个案件是慢的,不太清楚为什么.我怀疑这可能是由于R部分不必要的副本.您使用的是哪个版本的R?对于未来,请始终发布您的sessionInfo()输出.

  • @DavidArenburg,检查`data.table ::: \`[< - .data.table \``.我也没用过它.我认为没有人使用它.它必须仍然存在向后兼容性,不确定.任何和所有的改进也不会发生在那个功能(因为它不是惯用的)将是我的猜测. (2认同)