为什么 bindlist 比 bindlist“更好”?

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

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

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

98481 次浏览

rbindlistdo.call(rbind, list(...))的优化版本,众所周知 do.call(rbind, list(...))在使用 rbind.data.frame时速度较慢


它真正擅长的是什么

一些问题显示了 rbindlist的亮点在哪里

数据帧列表的快速向量化合并

无法使用 do.call 和 ldply 将大量的 data.frame (约100万)转换为单个 data.frame

这些都有基准,可以显示它的速度有多快。


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

Bindlist 的一些其他限制

曾经是很难与 factors打交道,因为后来修复了一个缺陷:

Rbindlist 两个 data.tables,其中一个具有 factor,另一个具有列 (Bug # 2650)的字符类型

它存在重复列名的问题

警告消息: in rbindlist (allargs) : 由强制引入的 NA: data.table? (臭虫2384号)中可能存在 bug


Frame 行名可能令人沮丧

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

您可以使用 do.call(rbind, list(...))处理混乱的行名 看

在 do.call 中使用 rbind 时如何避免重命名行?


内存效率

就内存而言,rbindlist是在 C中实现的,因此内存效率也很高,它使用 setattr通过引用来设置属性

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

v1.9.2时,rbindlist已经进化了不少,实现了许多功能,包括:

  • v1.9.2中关闭 FR # 2456Bug # 4981时,选择列的最高 SEXPTYPE
  • 正确处理 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。唯一的区别是,为了向后兼容,rbind.data.table默认使用 use.names=TRUE

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

然而,rbindlist缺乏某些特性(比如检查因子水平或匹配名称) ,因此它比 rbind.data.frame快得多,这一点只占很小的比重(或者说没有比重)。这是因为它们是在 C 语言中精心实现的,为速度和内存进行了优化。

下面是一个基准测试,它突出显示了高效绑定,同时使用 rbindlistuse.names特性从 v1.9.3按列名进行匹配。数据集由10000个 data.frame 组成,每个帧的大小为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秒。与基础解决方案相比,这是14倍的速度,18倍的速度比 dplyr的版本。

那么这也许应该被认为是一个错误?

# let us make a very simple list here
l <- list('a' = 1, 'b' = 2, 'c' = 3)
l
$a
[1] 1


$b
[1] 2


$c
[1] 3


# check that it is a list
class(l)
#[1] "list"
typeof(l)
#[1] "list"

而且 rbind可以处理它没有任何问题

do.call('rbind', l)
#    [,1]
# a    1
# b    2
# c    3

但是,当使用 rbindlist的人得到这个?

rbindlist(l)
Error in rbindlist(l) :
Item 1 of input is not a data.frame, data.table or list

由于我们在上面检查了输入是 list,所以错误消息非常令人困惑,不是吗?
对于这种最简单的情况,该函数的正确应用是否有文档记录?
任何提示都是值得赞赏的... 因为我期待与 do.call('rbind', l)相似或相同的结果,我有点困惑,为什么 data.table函数决定在同一个列表上转换等效调用的结果,当我处理错误的类错误时,例如通过这样做?

rbindlist(list(l))
# will result in
a b c
1: 1 2 3