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.table
le set
fonctionnement rapide de et associez-le au doublement manuel de la table en cas de besoin.
Utilisez RSQLite
et ajoutez au tableau conservé en mémoire.
data.frame
La 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_element
put in.
append(object, element)
qui ajoute le element
à la fin du tableau (représenté par object
).
access(object)
obtient le data.frame
avec 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 rowcount
attribut.
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 RSQLite
solution
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.frame
propre 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.call
au 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::set
et développez la table data.table de manière exponentielle (par exemple en utilisant mon code).