【问题标题】:What is the fastest way to get a vector of sorted unique values from a data.table?从 data.table 中获取排序唯一值向量的最快方法是什么?
【发布时间】:2016-08-25 11:34:00
【问题描述】:

answer 到这个question (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 会更大。我在 SO 上进行了一些搜索,但到目前为止还没有找到特定的答案。

【问题讨论】:

  • 您可以使用:ptm &lt;- proc.time()your codeproc.time() - ptm 对代码进行计时,因此您会找到最快的代码。 (请注意,这些是 3 行代码,注释显示为 1 行……请参阅 ats.ucla.edu/stat/r/faq/timing_code.htm 了解更多信息)。
  • @Gracos 感谢您的建议。在这里,microbenchmark 包是更好的选择,因为它允许在一个步骤中对多个表达式进行基准测试。
  • 你是对的。 proc.time() 只是一个更简单的选择。

标签: r dataframe data.table


【解决方案1】:

使用kit::funiquecollapse::funique 可能是获取已排序唯一值向量的快速方法

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

setDTthreads(1)
bench::mark(
dt = x[,logical(1), keyby = company]$company,
base = sort(unique(x$company)),
collapse = collapse::funique(x$company, TRUE),
kit = sort(kit::funique(x$company)))
#  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
#  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
#1 dt          130.4ms  141.5ms      7.17   49.63MB     2.39     3     1      419ms
#2 base        170.1ms  170.1ms      5.88  166.15MB    17.6      1     3      170ms
#3 collapse     51.8ms     52ms     19.1     2.49KB     0       10     0      524ms
#4 kit          17.6ms   17.8ms     56.0         0B     0       28     0      500ms

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

setDTthreads(4)
bench::mark(
dt = x[,logical(1), keyby = company]$company,
base = sort(unique(x$company)),
collapse = collapse::funique(x$company, TRUE),
kit = sort(kit::funique(x$company)))
#  expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time
#  <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm>
#1 dt           50.2ms   51.1ms     18.9    49.63MB     3.78    10     2      529ms
#2 base        147.1ms  161.3ms      6.26  166.15MB     6.26     4     4      639ms
#3 collapse     51.8ms   52.5ms     19.1     2.49KB     0       10     0      524ms
#4 kit          17.7ms   17.8ms     55.9         0B     0       28     0      501ms

使用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(collapse::funique(x$company))
#       User      System verstrichen 
#      0.053       0.000       0.053 

system.time(kit::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 目前最快,其次是collapse::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))

【讨论】:

    【解决方案2】:

    为了进行基准测试,创建了一个更大的data.table,包含 1.000.000 行:

    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)])
    

    将进行基准测试,尽管很明显排序唯一值应该比其他方式更快。

    此外,还包括来自此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)。

    正如预期的那样,变体 #4 和 #5(排序后唯一)非常慢。 编辑:#8 是最快的,它证实了 Frank 的评论。

    变种 #3 让我有点意外。尽管data.table 的快速基数排序比#1 和#2 效率低。好像是先排序再提取唯一值。

    基准测试,由company 键入的data.table

    受此观察的启发,我使用data.tablecompany 为键重复了基准测试。

    setkeyv(salesdt, "company")
    

    时间显示(请不要时间轴的比例变化)#4 和 #5 已通过键控显着加速。它们甚至比#3 还要快。 请注意,变体 #8 的时间安排包含在下一节中。

    基准测试,稍作调整

    变体#3 仍然包含order(company),如果已经由company 键入,则没有必要。所以,我从#3 和#5 中删除了对ordersort 的不必要调用:

    timings <- 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
    )
    

    时间现在在同一级别上显示变体 #1 到 #4。 编辑: 同样,#8(Frank 的解决方案)是最快的。

    警告:基准测试基于仅包含 5 个不同字母作为公司名称的原始数据。如果有更多不同的公司名称,结果可能会有所不同。已经用data.table v.1.9.7得到了结果。

    【讨论】:

    • 呵呵,为什么你有时会取.N 或求和sales,这对于找到company 的不同值是完全没有必要的?唯一合理的比较是timings &lt;- microbenchmark::microbenchmark( salesdt[,logical(1), keyby=company]$company, sort(unique(salesdt$company)) ); ggplot2::autoplot(timings)
    • 因为它不仅仅是获取不同的值。除了按字母顺序排列公司名称之外,还有其他排序选项。可能是,这背后的原因从stackoverflow.com/a/36951561/3817004 变得更加清晰。
    • 好的,但是(1)你的问题没有出现在这里,这只与我的基准测试中的两件事有关; (2) 字典排序和基于计数和总和等聚合的排序之间的比较没有意义,因为当你必须计算这些聚合时显然需要更长的时间; (3) 真的存在如何最好地按计数/.N 或总和排序的问题吗?你只展示了一种方法,我认为它可能是最好的......我想你也可以tapplysort 并提取名称,尽管这只是有意义的,因为company 恰好是一个字符串.
    • 无论如何,关于这里提出的问题,您缺少salesdt[,logical(1), keyby=company]$company,以我的计算机为例,这是最快的。您可以使用任何其他长度为一的向量来代替 logical(1)
    • @Frank 谢谢你的建议。我已经重复了包括您的代码在内的基准测试。而且 - 你是对的!
    【解决方案3】:

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

    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 提供的信息不如microbenchmark 全面,但更简单。

    上面的输出是:

    sort(salesdt[, unique(company)])
    user  system elapsed 
    0.05    0.02    0.06 
    
    sort(unique(salesdt$company))
    user  system elapsed 
    0.01    0.01    0.03 
    
    salesdt[order(company), unique(company)]
    user  system elapsed 
    0.03    0.02    0.05 
    

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

    【讨论】:

    • 非常感谢您使用proc.time 重复基准测试。关于 microbenchmark 的使用,我遵循 Hadly Wickham 的书 Advanced R 中的建议。 SO上还有一个很好的解释here
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-16
    • 1970-01-01
    • 2012-04-25
    • 1970-01-01
    • 2017-11-29
    • 1970-01-01
    相关资源
    最近更新 更多