【发布时间】:2013-03-18 09:38:33
【问题描述】:
我正在浏览data.table 的文档,并且还从这里的一些对话中注意到rbindlist 应该比rbind 更好。
我想知道为什么rbindlist 比rbind 更好,在哪些情况下rbindlist 真的优于rbind?
在内存利用率方面有什么优势吗?
【问题讨论】:
标签: r data.table rbind rbindlist
我正在浏览data.table 的文档,并且还从这里的一些对话中注意到rbindlist 应该比rbind 更好。
我想知道为什么rbindlist 比rbind 更好,在哪些情况下rbindlist 真的优于rbind?
在内存利用率方面有什么优势吗?
【问题讨论】:
标签: r data.table rbind rbindlist
rbindlist 是do.call(rbind, list(...)) 的优化版本,以使用rbind.data.frame 时速度慢而著称
一些问题表明rbindlist 的亮点在哪里
Fast vectorized merge of list of data.frames by row
这些具有显示速度有多快的基准。
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
它过去很难处理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)
rbindlist 可以处理lists data.frames 和data.tables,并且会返回一个没有行名的data.table
您可以使用 do.call(rbind, list(...)) 进入混乱的行名
见
How to avoid renaming of rows when using rbind inside do.call?
在内存方面rbindlist是在C中实现的,所以内存效率很高,它使用setattr通过引用来设置属性
rbind.data.frame 在R 中实现,它执行大量分配,并使用attr<-(以及class<- 和rownames<-,所有这些都将(在内部)创建创建的data.frame 的副本。
【讨论】:
attr<-、class<- 和(我认为)rownames<- 都在原地修改。
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 对象,那么 rbind 和 rbindlist 之间的性能差异可能很小。
dplyr::rbind_list 也很相似
v1.9.2,rbindlist 发展了很多,实现了许多功能,包括:
此外,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.3 的rbindlist 的use.names 功能。数据集由 10000 个数据帧组成,每个数据帧大小为 10*500。
注意:此基准已更新,包括与 dplyr 的 bind_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 倍。
【讨论】: