Suponga que simplemente no conoce el tamaño del data.frame de antemano. Bien pueden ser unas pocas filas o unos pocos millones. Necesita tener algún tipo de contenedor, que crezca dinámicamente. Teniendo en cuenta mi experiencia y todas las respuestas relacionadas en SO, vengo con 4 soluciones distintas:
rbindlist
al data.frame
Utilice data.table
la set
operación rápida y combínela con doblar manualmente la mesa cuando sea necesario.
Use RSQLite
y agregue a la tabla que se guarda en la memoria.
data.frame
La propia capacidad de crecer y usar un entorno personalizado (que tiene semántica de referencia) para almacenar el data.frame para que no se copie al regresar.
Aquí hay una prueba de todos los métodos para un número pequeño y grande de filas agregadas. Cada método tiene 3 funciones asociadas:
create(first_element)
que devuelve el objeto de respaldo apropiado con first_element
put in.
append(object, element)
que agrega el element
al final de la tabla (representado por object
).
access(object)
obtiene el data.frame
con todos los elementos insertados.
rbindlist
al data.frame
Eso es bastante fácil y sencillo:
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
+ doblando manualmente la mesa cuando sea necesario.
Almacenaré la longitud real de la tabla en un 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,]))
}
SQL debería estar optimizado para una rápida inserción de registros, por lo que inicialmente tenía grandes esperanzas de RSQLite
solución
Esto es básicamente copiar y pegar la respuesta de Karsten W. en un hilo similar.
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
propio entorno personalizado de adición de filas.
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 pruebas:
Por conveniencia, usaré una función de prueba para cubrirlos todos con llamadas indirectas. (Lo comprobé: usar en do.call
lugar de llamar a las funciones directamente no hace que el código se ejecute durante más tiempo).
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)))
}
Veamos el rendimiento para n = 10 inserciones.
También agregué funciones de 'placebo' (con sufijo 0
) que no realizan nada, solo para medir la sobrecarga de la configuración de la prueba.
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 1E5 filas (mediciones realizadas en CPU Intel (R) Core (TM) 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 la sulution basada en SQLite, aunque recupera algo de velocidad en datos grandes, no está ni cerca de data.table + crecimiento exponencial manual. ¡La diferencia es de casi dos órdenes de magnitud!
Resumen
Si sabe que agregará un número bastante pequeño de filas (n <= 100), siga adelante y use la solución más simple posible: simplemente asigne las filas al data.frame usando la notación entre corchetes e ignore el hecho de que el data.frame es no poblado previamente.
Para todo lo demás, use data.table::set
y haga crecer el data.table exponencialmente (por ejemplo, usando mi código).