【问题标题】:Why is rbindlist "better" than rbind?为什么 rbindlist 比 rbind “更好”?
【发布时间】:2013-03-18 09:38:33
【问题描述】:

我正在浏览data.table 的文档,并且还从这里的一些对话中注意到rbindlist 应该比rbind 更好。

我想知道为什么rbindlistrbind 更好,在哪些情况下rbindlist 真的优于rbind

在内存利用率方面有什么优势吗?

【问题讨论】:

    标签: r data.table rbind rbindlist


    【解决方案1】:

    rbindlistdo.call(rbind, list(...)) 的优化版本,以使用rbind.data.frame 时速度慢而著称


    它真正擅长的地方

    一些问题表明rbindlist 的亮点在哪里

    Fast vectorized merge of list of data.frames by row

    Trouble converting long list of data.frames (~1 million) to single data.frame using do.call and ldply

    这些具有显示速度有多快的基准。


    rbind.data.frame 很慢,这是有原因的

    rbind.data.frame 会进行大量检查,并且会按名称匹配。 (即 rbind.data.frame 将说明列可能有不同的顺序,并按名称匹配),rbindlist 不做这种检查,而是按位置加入

    例如

    do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3)))
    ##    a b
    ## 1  1 2
    ## 2  2 3
    ## 3  2 1
    ## 4  3 2
    
    rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6)))
    ##     a b
    ##  1: 1 2
    ##  2: 2 3
    ##  3: 1 2
    ##  4: 2 3
    

    rbindlist 的一些其他限制

    过去很难处理factors,因为一个已经修复的错误:

    rbindlist two data.tables where one has factor and other has character type for a column (Bug #2650)

    存在重复列名的问题

    Warning message: in rbindlist(allargs) : NAs introduced by coercion: possible bug in data.table? (Bug #2384)


    rbind.data.frame 行名可能令人沮丧

    rbindlist 可以处理lists data.framesdata.tables,并且会返回一个没有行名的data.table

    您可以使用 do.call(rbind, list(...)) 进入混乱的行名 见

    How to avoid renaming of rows when using rbind inside do.call?


    内存效率

    在内存方面rbindlist是在C中实现的,所以内存效率很高,它使用setattr通过引用来设置属性

    rbind.data.frameR 中实现,它执行大量分配,并使用attr<-(以及class<-rownames<-,所有这些都将(在内部)创建创建的data.frame 的副本。

    【讨论】:

    • 仅供参考attr<-class<- 和(我认为)rownames<- 都在原地修改。
    • @hadley 你确定吗?试试DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF))
    • rbind.data.frame 具有特殊的“劫持”逻辑——当它的第一个参数是data.table 时,它会调用.rbind.data.table,它会进行一些检查,然后在内部调用rbindlist。因此,如果您已经有要绑定的 data.table 对象,那么 rbindrbindlist 之间的性能差异可能很小。
    • mnel,这篇文章可能需要编辑,因为rbindlist 能够按名称匹配 (use.names=TRUE) 并填充缺失的列 (fill=TRUE)。我更新了thisthisthis 的帖子。你介意编辑这个吗?或者如果我这样做可以吗?无论哪种方式我都可以。
    • dplyr::rbind_list 也很相似
    【解决方案2】:

    v1.9.2rbindlist 发展了很多,实现了许多功能,包括:

    • 在绑定时选择列中最高的SEXPTYPE - 在v1.9.2 中实现,关闭FR #2456Bug #4981
    • 正确处理 factor 列 - 首先在 v1.8.10 关闭 Bug #2650 中实现,并扩展到在 v1.9.2 中仔细绑定 有序 因素,关闭 FR #4856Bug #5019

    此外,v1.9.2 中,rbind.data.table 还获得了一个 fill 参数,允许通过填充缺失的列进行绑定,在 R 中实现。

    现在在v1.9.3,对这些现有功能进行了更多改进:

    • rbindlist 获得一个参数 use.names,默认为 FALSE 以实现向后兼容性。
    • rbindlist 还获得一个参数 fill,默认情况下也是 FALSE 以实现向后兼容性。
    • 这些功能都是用 C 语言实现的,并且在添加功能时要小心编写,以免影响速度。
    • 由于rbindlist 现在可以按名称匹配并填充缺失的列,所以rbind.data.table 现在只需调用rbindlist。唯一的区别是use.names=TRUE 默认为rbind.data.table,为了向后兼容。

    rbind.data.frame 减慢了很多,主要是由于可以避免的副本(@mnel 也指出了这一点)(通过移动到 C)。我认为这不是唯一的原因。当每个 data.frame 有很多列并且有很多这样的 data.frame 需要绑定时,检查/匹配rbind.data.frame 中的列名的实现也会变慢(如下面的基准测试所示)。

    但是,rbindlist 缺少(ed)某些功能(例如检查因子级别或匹配名称)对它比rbind.data.frame 更快的影响很小(或没有)。这是因为它们是用 C 语言精心实现的,针对速度和内存进行了优化。

    这是一个基准测试,它突出显示了高效绑定,同时通过列名匹配以及使用来自v1.9.3rbindlistuse.names 功能。数据集由 10000 个数据帧组成,每个数据帧大小为 10*500。

    注意:此基准已更新,包括与 dplyrbind_rows 的比较

    library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC
    library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC
    set.seed(1L)
    names = paste0("V", 1:500)
    cols = 500L
    foo <- function() {
        data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10))))
        setnames(data, sample(names))
    }
    n = 10e3L
    ll = vector("list", n)
    for (i in 1:n) {
        .Call("Csetlistelt", ll, i, foo())
    }
    
    system.time(ans1 <- rbindlist(ll))
    #  user  system elapsed 
    # 1.226   0.070   1.296 
    
    system.time(ans2 <- rbindlist(ll, use.names=TRUE))
    #  user  system elapsed 
    # 2.635   0.129   2.772 
    
    system.time(ans3 <- do.call("rbind", ll))
    #   user  system elapsed 
    # 36.932   1.628  38.594 
    
    system.time(ans4 <- bind_rows(ll))
    #   user  system elapsed 
    # 48.754   0.384  49.224 
    
    identical(ans2, setDT(ans3)) 
    # [1] TRUE
    identical(ans2, setDT(ans4))
    # [1] TRUE
    

    在不检查名称的情况下绑定列只需要 1.3 秒,而检查列名和适当绑定只需要多花 1.5 秒。与基本解决方案相比,这比 dplyr 的版本快 14 倍,快 18 倍。

    【讨论】:

      猜你喜欢
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-04
      • 2015-06-27
      • 1970-01-01
      • 2012-01-02
      相关资源
      最近更新 更多