What is the fastest way to get a vector of sorted unique values from a data.table?

后端 未结 2 1597
借酒劲吻你
借酒劲吻你 2021-01-07 05:04

The answer to this question (Unique sorted rows single column from R data.table) suggested three different ways to get a vector of sorted unique values from a data.tab

相关标签:
2条回答
  • 2021-01-07 05:34

    Alternatively you could do the following:

    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
    

    Information provided by proc.time is not as thorough as microbenchmark, but it is simpler.

    Output for the above is:

    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 
    

    Where user time relates to code execution, system time to CPU, and elapsed time is the difference since starting the stopwatch (and will be equal to the sum of user and system times if code run altogether). (taken from http://www.ats.ucla.edu/stat/r/faq/timing_code.htm)

    0 讨论(0)
  • 2021-01-07 05:50

    For benchmarking, a larger data.table is created with 1.000.000 rows:

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

    For the sake of completeness also the variants

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

    will be benchmarked although it seems to be obvious that sorting unique values should be faster than the other way around.

    In addition, two other sort options from this answer are included:

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

    Edit: Following from Frank's comment, I've included his suggestion:

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

    Benchmarking, no key set

    Benchmarking is done with help of the microbenchmark package:

    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
    )
    

    The timings are displayed with

    ggplot2::autoplot(timings)
    

    Please, note the reverse order in the chart (#1 at bottom, #8 at top).

    As expected, variants #4 and #5 (unique after sort) are pretty slow. Edit: #8 is the fastest which confirms Frank's comment.

    A bit of surprise to me was variant #3. Despite data.table's fast radix sort it is less efficient than #1 and #2. It seems to sort first and then to extract the unique values.

    Benchmarking, data.table keyed by company

    Motivated by this observation I repeated the benchmark with the data.table keyed by company.

    setkeyv(salesdt, "company")
    

    The timings show (please not the change in scale of the time axis) that #4 and #5 have been accelerated dramatically by keying. They are even faster than #3. Note that timings for variant #8 are included in the next section.

    Benchmarking, keyed with a bit of tuning

    Variant #3 still includes order(company) which isn't necessary if already keyed by company. So, I removed the unnecessary calls to order and sort from #3 and #5:

    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
    )
    

    The timings now show variants #1 to #4 on the same level. Edit: Again, #8 (Frank's solution) is the fastests.

    Caveat: The benchmarking is based on the original data which only includes 5 different letters as company names. It is likely that the result will look differently with a larger number of distinct company names. The results have been obtained with data.table v.1.9.7.

    0 讨论(0)
提交回复
热议问题