Supposons que vous ne connaissiez tout simplement pas la taille du data.frame à l'avance. Il peut s'agir de quelques lignes ou de quelques millions. Vous devez avoir une sorte de conteneur, qui se développe dynamiquement. En tenant compte de mon expérience et de toutes les réponses connexes en SO, je viens avec 4 solutions distinctes:
rbindlist au data.frame
Utilisez data.tablele setfonctionnement rapide de et associez-le au doublement manuel de la table en cas de besoin.
Utilisez RSQLiteet ajoutez au tableau conservé en mémoire.
data.frameLa propre capacité de développer et d'utiliser un environnement personnalisé (qui a une sémantique de référence) pour stocker le data.frame afin qu'il ne soit pas copié au retour.
Voici un test de toutes les méthodes pour le petit et le grand nombre de lignes ajoutées. Chaque méthode est associée à 3 fonctions:
create(first_element)qui renvoie l'objet de support approprié avec first_elementput in.
append(object, element)qui ajoute le elementà la fin du tableau (représenté par object).
access(object)obtient le data.frameavec tous les éléments insérés.
rbindlist au data.frame
C'est assez simple et simple:
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 + doubler manuellement la table en cas de besoin.
Je vais stocker la vraie longueur de la table dans un rowcountattribut.
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 devrait être optimisé pour une insertion rapide des enregistrements, donc j'avais au départ de grands espoirs de RSQLitesolution
Il s'agit essentiellement d'un copier-coller de la réponse de Karsten W. sur un fil similaire.
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.framepropre environnement personnalisé d'ajout de lignes.
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)
}
La suite de tests:
Pour plus de commodité, j'utiliserai une fonction de test pour les couvrir tous avec un appel indirect. (J'ai vérifié: utiliser do.callau lieu d'appeler directement les fonctions ne rend pas le code mesurable plus longtemps).
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)))
}
Voyons les performances pour n = 10 insertions.
J'ai également ajouté des fonctions «placebo» (avec suffixe 0) qui n'effectuent rien - juste pour mesurer la surcharge de la configuration de test.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)


Pour les lignes 1E5 (mesures effectuées sur un processeur Intel (R) Core (TM) i7-4710HQ à 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
Il semble que la solution basée sur SQLite, bien qu'elle regagne un peu de vitesse sur des données volumineuses, est loin d'être proche de data.table + croissance exponentielle manuelle. La différence est de près de deux ordres de grandeur!
Résumé
Si vous savez que vous allez ajouter un nombre assez petit de lignes (n <= 100), allez-y et utilisez la solution la plus simple possible: affectez simplement les lignes au data.frame en utilisant la notation entre crochets et ignorez le fait que le data.frame est non prérempli.
Pour tout le reste, utilisez data.table::setet développez la table data.table de manière exponentielle (par exemple en utilisant mon code).