从data.table获取已排序的唯一值向量的最快方法是什么?

时间:2016-04-30 09:17:27

标签: r dataframe data.table

answerquestion (Unique sorted rows single column from R data.table)提出了三种不同的方法来从data.table获取已排序的唯一值的向量:

# 1
sort(salesdt[, unique(company)])
#2 
sort(unique(salesdt$company))
#3
salesdt[order(company), unique(company)]

另一个answer提出了除字典顺序之外的其他排序选项:

salesdt[, .N, by = company][order(-N), company]
salesdt[, sum(sales), by = company][order(-V1), company]

data.table

创建
library(data.table)
company <- c("A", "S", "W", "L", "T", "T", "W", "A", "T", "W")
item <- c("Thingy", "Thingy", "Widget", "Thingy", "Grommit", 
          "Thingy", "Grommit", "Thingy", "Widget", "Thingy")
sales <- c(120, 140, 160, 180, 200, 120, 140, 160, 180, 200)
salesdt <- data.table(company,item,sales) 

与往常一样,如果有不同的选项可供选择,我开始想知道最佳解决方案是什么,特别是如果data.table会更大。我已经搜索了一下,但到目前为止还没有找到一个特别的答案。

3 个答案:

答案 0 :(得分:6)

对于基准测试,使用1.000.000行创建更大的data.table

n <- 1e6
set.seed(1234) # to reproduce the data
salesdt <- data.table(company = sample(company, n, TRUE), 
                      item = sample(item, n, TRUE), 
                      sales = sample(sales, n, TRUE))

为了完整起见,还有变体

# 4
unique(sort(salesdt$company))
# 5
unique(salesdt[,sort(company)])

将进行基准测试,虽然看起来很明显排序唯一值应该比其他方式更快。<​​/ p>

此外,还包括此answer中的其他两个排序选项:

# 6
salesdt[, .N, by = company][order(-N), company]
# 7
salesdt[, sum(sales), by = company][order(-V1), company]

编辑:根据弗兰克的评论,我已经包含了他的建议:

# 8
salesdt[,logical(1), keyby = company]$company

基准测试,无密钥集

基准测试是在microbenchmark包的帮助下完成的:

timings <- microbenchmark::microbenchmark(
  sort(salesdt[, unique(company)]),
  sort(unique(salesdt$company)),
  salesdt[order(company), unique(company)],
  unique(sort(salesdt$company)),
  unique(salesdt[,sort(company)]),
  salesdt[, .N, by = company][order(-N), company],
  salesdt[, sum(sales), by = company][order(-V1), company],
  salesdt[,logical(1), keyby = company]$company
)

时间以

显示
ggplot2::autoplot(timings)

请注意图表中的反向顺序(底部#1,顶部#8)。

enter image description here

正如预期的那样,变体#4和#5(排序后唯一)非常慢。 编辑#8是最快确认弗兰克评论的。

对我来说有点意外的是变种#3。尽管data.table的快速基数排序,它的效率低于#1和#2。它似乎首先排序,然后提取唯一值。

基准测试,由company

键入的data.table

受此观察的启发,我使用data.table键入的company重复了基准。

setkeyv(salesdt, "company")

时间显示(请不要时间轴的规模变化)#4和#5通过键控显着加速。它们比#3更快。 请注意,变体#8的时间安排包含在下一部分中。

enter image description here

基准测试,通过一些调整

变体#3仍包含order(company),如果已由company键入,则不需要order。所以,我从#3和#5中删除了对sorttimings <- microbenchmark::microbenchmark( sort(salesdt[, unique(company)]), sort(unique(salesdt$company)), salesdt[, unique(company)], unique(salesdt$company), unique(salesdt[, company]), salesdt[, .N, by = company][order(-N), company], salesdt[, sum(sales), by = company][order(-V1), company], salesdt[,logical(1), keyby = company]$company ) 的不必要的调用:

data.table v.1.9.7

时间现在显示同一级别的变体#1到#4。 编辑:再次,#8(弗兰克的解决方案)是紧固件。

enter image description here

警告:基准测试基于原始数据,仅包含5个不同的字母作为公司名称。对于大量不同的公司名称,结果可能会有所不同。结果是使用file_put_contents()获得的。

答案 1 :(得分:1)

或者您可以执行以下操作:

ViewResolver

Warning: No mapping found in DispatcherServlet with name 'dispatcher'提供的信息不如library(data.table) n <- 1e6 salesdt <- data.table(company = sample(company, n, TRUE), item = sample(item, n, TRUE), sales = sample(sales, n, TRUE)) ptm <- proc.time() sort(salesdt[, unique(company)]) proc.time() - ptm ptm <- proc.time() sort(unique(salesdt$company)) proc.time() - ptm ptm <- proc.time() salesdt[order(company), unique(company)] proc.time() - ptm 详尽,但更简单。

以上输出为:

proc.time

用户时间与代码执行有关,CPU的系统时间和经过的时间是启动秒表后的差异(如果代码完全运行,则等于用户和系统时间的总和)。 (取自http://www.ats.ucla.edu/stat/r/faq/timing_code.htm

答案 2 :(得分:0)

使用 funique 中的 kit 可能是一种获得排序的唯一值向量的快速方法。

还应该考虑使用了多少胎面。在 data.table 中使用一个核心:

setDTthreads(1)
bench::mark(
x[,logical(1), keyby = company]$company,
sort(unique(x$company)),
sort(funique(x$company))
)
#  expression                                    min   median `itr/sec` mem_alloc
#  <bch:expr>                               <bch:tm> <bch:tm>     <dbl> <bch:byt>
#1 x[, logical(1), keyby = company]$company  125.8ms  126.2ms      7.90   51.21MB
#2 sort(unique(x$company))                   146.6ms  146.6ms      6.82  166.17MB
#3 sort(funique(x$company))                   17.3ms   17.6ms     56.7     2.32KB

在 data.table 中使用四个核心:

setDTthreads(4)
bench::mark(
x[,logical(1), keyby = company]$company,
sort(unique(x$company)),
sort(funique(x$company))
)
#  expression                                    min   median `itr/sec` mem_alloc
#  <bch:expr>                               <bch:tm> <bch:tm>     <dbl> <bch:byt>
#1 x[, logical(1), keyby = company]$company   50.4ms   51.3ms     19.5     49.6MB
#2 sort(unique(x$company))                   145.9ms  145.9ms      6.85   166.1MB
#3 sort(funique(x$company))                   17.3ms   17.5ms     56.9         0B

当使用system.time时,每个核心使用的时间相加:

setDTthreads(1)
system.time(x[,logical(1), keyby = company]$company)
#       User      System verstrichen 
#      0.122       0.004       0.126 

setDTthreads(4)
system.time(x[,logical(1), keyby = company]$company)
#       User      System verstrichen 
#      0.150       0.028       0.052 

system.time(funique(x$company))
#       User      System verstrichen 
#      0.018       0.000       0.018 

在使用密钥时,还应考虑创建密钥的时间

system.time(setkeyv(x, "company"))
#       User      System verstrichen 
#      0.241       0.012       0.253 

看起来 kit::funique 目前是最快的。使用一个线程 base::unique 比使用 data.table 慢一点。

数据和图书馆:

set.seed(42)
n <- 1e7
company <- c("A", "S", "W", "L", "T", "T", "W", "A", "T", "W")
item <- c("Thingy", "Thingy", "Widget", "Thingy", "Grommit", 
          "Thingy", "Grommit", "Thingy", "Widget", "Thingy")
sales <- c(120, 140, 160, 180, 200, 120, 140, 160, 180, 200)

library(data.table)
x <- data.table(company = sample(company, n, TRUE), 
                      item = sample(item, n, TRUE), 
                sales = sample(sales, n, TRUE))

library(kit)