假设您根本不知道data.frame的大小。它可以是几行,也可以是几百万。您需要具有某种可以动态增长的容器。考虑到我的经验以及SO中的所有相关答案,我提供了4种不同的解决方案:
rbindlist
到data.frame
使用data.table
的快速set
操作,并在需要时将其与手动将表加倍。
使用RSQLite
并追加到内存中保存的表。
data.frame
自身的成长和使用自定义环境(具有参考语义)来存储data.frame的能力,因此不会在返回时将其复制。
这是对大量和少量附加行的所有方法的测试。每种方法都有3个与之关联的功能:
create(first_element)
返回first_element
放入的适当支持对象。
append(object, element)
将追加element
到表格的末尾(由表示object
)。
access(object)
获取data.frame
所有插入的元素。
rbindlist
到data.frame
这非常简单明了:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table::set
+在需要时手动将表格加倍。
我将表的真实长度存储在一个rowcount
属性中。
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
应该为快速记录插入而优化SQL,因此我最初对RSQLite
解决方案寄予厚望
这基本上是在类似线程上复制和粘贴Karsten W.答案。
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frame
自己的行附加+自定义环境。
create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
测试套件:
为方便起见,我将使用一个测试函数通过间接调用将它们全部覆盖。(我检查过:使用do.call
而不是直接调用函数不会使代码的运行时间更长)。
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
让我们看看n = 10插入的性能。
我还添加了一个“安慰剂”功能(带有后缀0
),该功能什么都不做-只是为了衡量测试设置的开销。
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
对于1E5行(在2.50 GHz @Intel®Core™i7-4710HQ CPU上完成的测量):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
看起来像基于SQLite的解决方案,尽管可以在大数据上重新获得一定的速度,但远不及data.table +手动指数增长。区别几乎是两个数量级!
摘要
如果您知道要追加的行数很少(n <= 100),请继续使用最简单的解决方案:只需使用方括号表示法将行分配给data.frame,而忽略data.frame是未预先填充。
对于其他一切,请使用data.table::set
和增长data.table(例如,使用我的代码)。