在您的最后一个案例中,这是 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% 的任何后续子集都将非常快速(Slides、R script、video,来自 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() 输出。