Suponha que você simplesmente não saiba o tamanho do data.frame com antecedência. Pode muito bem ser algumas linhas ou alguns milhões. Você precisa ter algum tipo de recipiente, que cresça dinamicamente. Levando em consideração minha experiência e todas as respostas relacionadas no SO, venho com 4 soluções distintas:
rbindlist
para o data.frame
Use data.table
a set
operação rápida e junte-a à duplicação manual da mesa, quando necessário.
Use RSQLite
e acrescente à tabela mantida na memória.
data.frame
própria capacidade de crescer e usar o ambiente personalizado (que tem semântica de referência) para armazenar o data.frame, para que ele não seja copiado no retorno.
Aqui está um teste de todos os métodos para o número pequeno e grande de linhas anexadas. Cada método possui 3 funções associadas:
create(first_element)
que retorna o objeto de suporte apropriado com a first_element
inserção.
append(object, element)
que anexa element
ao final da tabela (representado por object
).
access(object)
obtém o data.frame
com todos os elementos inseridos.
rbindlist
para o data.frame
Isso é bastante fácil e direto:
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
+ dobrar manualmente a tabela quando necessário.
Vou armazenar o comprimento verdadeiro da tabela em um rowcount
atributo.
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,]))
}
O SQL deve ser otimizado para inserção rápida de registros, então eu inicialmente tinha grandes esperanças de RSQLite
solução
Isso é basicamente copiar e colar a resposta de Karsten W. em tópicos semelhantes.
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
próprio ambiente personalizado + com adição de linhas.
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)
}
A suíte de testes:
Por conveniência, usarei uma função de teste para cobrir todas elas com chamadas indiretas. (Eu verifiquei: usar em do.call
vez de chamar diretamente as funções não torna o código mensurável por mais tempo).
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)))
}
Vamos ver o desempenho de n = 10 inserções.
Também adicionei funções 'placebo' (com sufixo 0
) que não realizam nada - apenas para medir a sobrecarga da configuração do teste.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Para linhas 1E5 (medições feitas na CPU Intel (R) Core (i) i7-4710HQ a 2,50 GHz):
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
Parece que a suluição baseada em SQLite, embora recupere alguma velocidade em dados grandes, não chega nem perto de data.table + crescimento exponencial manual. A diferença é quase duas ordens de magnitude!
Resumo
Se você souber que anexará um número bastante pequeno de linhas (n <= 100), vá em frente e use a solução mais simples possível: apenas atribua as linhas ao data.frame usando a notação entre colchetes e ignore o fato de que o data.frame é não pré-preenchido.
Para todo o resto, use data.table::set
e aumente exponencialmente o data.table (por exemplo, usando meu código).