【问题标题】:Why is expand.grid faster than data.table 's CJ?为什么 expand.grid 比 data.table 的 CJ 快?
【发布时间】:2012-10-08 13:25:06
【问题描述】:
> system.time(expand.grid(1:1000,1:10000))
   user  system elapsed 
   1.65    0.34    2.03 
> system.time(CJ(1:1000,1:10000))
   user  system elapsed 
   3.48    0.32    3.79 

【问题讨论】:

  • 可能是因为它在结果 data.table 上设置了键
  • 好点。我会回答我自己的问题。
  • CJ 在传递排序向量时可能会更快。感谢您的强调。现已提交FR#2321 CJ speedup by not setting key naively
  • @Michael,我想最好的答案是我更新了这个问题。如果你能改变它,那将是非常好的,这样未来的用户就不会因为没有被修复而误解它。谢谢。

标签: r data.table


【解决方案1】:

感谢您报告此事。这已在 data.table 1.8.9 中得到修复。这是最新提交 (913) 的计时测试:

system.time(expand.grid(1:1000,1:10000))
# user system elapsed
# 1.420 0.552 1.987

system.time(CJ(1:1000,1:10000))
# user system elapsed
# 0.080 0.092 0.171

来自NEWS

CJ() 在 1e6 行(例如)上快 90%,#4849。现在,输入在合并之前先排序,而不是在合并之后排序,并且使用 rep.int 而不是 rep(感谢 Sean Garborg 的想法、代码和基准测试),并且仅在 is.unsorted() 时才排序,#2321。

还可以查看新闻以了解其他值得注意的功能和错误修复;例如,CJ() 也获得了一个新的 sorted 参数。

【讨论】:

    【解决方案2】:

    Mnel 的猜测是正确的。 CJ 返回一个 data.table,其中每一列都是一个键。

    > DT <- CJ(1:100,1:100)
    > key(DT)
    [1] "V1" "V2"
    

    更公平的比较:

    > system.time(CJ(1:1000,1:10000))
       user  system elapsed 
       3.40    0.25    3.73 
    > system.time(data.table(expand.grid(1:1000,1:10000),key=c("Var1","Var2")))
       user  system elapsed 
       4.14    0.68    4.90 
    

    【讨论】:

    • +1 会改进CJ 即便如此;在对问题的评论中链接到 FR。顺便说一句,as.data.table 应该比 data.table 快。
    • 公平地说,您在第二行看到的差异来自data.table() 开销。如果您查看 Matthew 的建议,您会看到 expand.grid,即使在转换为 data.table 之后仍然快两倍
    • 不是没有keyas.data.table()吗?
    【解决方案3】:

    它慢的真正原因是它没有关注 CJ 的默认使用:当参数向量满足 anyDuplicated(vector) == F 时。

    也许其他人使用 CJ 的方式不同,但对于独特的向量,还有改进的余地:

    速度比较

    Unit: milliseconds
                      expr        min         lq     median         uq        max neval
        dt1 <- CJ(a, b, c) 2394.38929 2434.75660 2439.14362 2444.66607 2686.41990   100
    dt2 <- fastCJ(a, b, c)   18.83701   25.33339   25.51254   25.70966   27.60622   100
    Output identical: TRUE
    

    代码

    library(microbenchmark)
    library(data.table)
    
    repTE <- function(x, times, each) {
      rep.int(rep.int(x, times=rep.int(each, times=length(x))), times=times)
    }
    fastCJ <- function(...) {
      l <- lapply(list(...), sort.int, method="quick")
      seq_ct <- length(l)
      if (seq_ct > 1) {
        seq_lens <- vapply(l, length, numeric(1))
        tot_len <- prod(seq_lens)
    
        l <-lapply(
          seq_len(seq_ct),
          function(i) {
            if (i==1) {
              len <- seq_lens[1]
              rep.int(l[[1]], times=rep.int(tot_len/len, len))
            } else if (i < seq_ct) {
              pre_len <- prod(seq_lens[1:(i - 1)])
              repTE(l[[i]], times=pre_len, each=tot_len/pre_len/seq_lens[i])
            } else {
              rep.int(l[[seq_ct]], times=tot_len/seq_lens[seq_ct])
            }
          }
        )    
      } else {
        tot_len <- length(l[[1]])
      }
    
      setattr(l, "row.names", .set_row_names(tot_len))
      setattr(l, "class", c("data.table", "data.frame"))
      if (is.null(names <- names(seq_list))) {
        names <- vector("character", seq_ct)
      }
      if (any(tt <- names == "")) {
        names[tt] <- paste0("V", which(tt))
      }
      setattr(l, "names", names)
      data.table:::settruelength(l, 0L)
      l <- alloc.col(l)
      setattr(l, "sorted", names(l))
    
      return(l)
    }
    
    a <- factor(sample(1:1000, 1000))
    b <- sample(letters, 26)
    c <- runif(100)
    print(microbenchmark( dt1 <- CJ(a, b, c), dt2 <- fastCJ(a, b, c)))
    cat("Output identical:", identical(dt1, dt2))
    

    【讨论】:

    • Error in fastCJ(a, b, c) : object 'seq_list' not found
    • 一个清理过的版本(由@Arun 带头)已经替换了 R-Forge 上 data.table 中的“CJ”,所以我会去那里,或者等待它。功能请求:#4849; Faster CJ
    猜你喜欢
    • 2018-08-25
    • 2019-05-19
    • 1970-01-01
    • 2019-11-02
    • 2015-07-24
    • 1970-01-01
    相关资源
    最近更新 更多