Cómo agregar filas a un dataframe R

He buscado en StackOverflow, pero no encuentro una solución específica para mi problema, que implica agregar filas a un dataframe R.

Estoy inicializando un dataframe de 2 columnas vacías, de la siguiente manera.

df = data.frame(x = numeric(), y = character()) 

Luego, mi objective es recorrer una lista de valores y, en cada iteración, agregar un valor al final de la lista. Empecé con el siguiente código.

 for (i in 1:10) { df$x = rbind(df$x, i) df$y = rbind(df$y, toString(i)) } 

También intenté las funciones c , append y merge sin éxito. Por favor, hágamelo saber si tiene alguna sugerencia.

Actualizar

Sin saber lo que estás tratando de hacer, compartiré una sugerencia más: asigna previamente vectores del tipo que deseas para cada columna, inserta valores en esos vectores y luego, al final, crea tu data.frame .

Continuando con Julian’s f3 (un data.frame preasignado) como la opción más rápida hasta el momento, definida como:

 # pre-allocate space f3 <- function(n){ df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE) for(i in 1:n){ df$x[i] <- i df$y[i] <- toString(i) } df } 

Aquí hay un enfoque similar, pero uno donde data.frame se crea como el último paso.

 # Use preallocated vectors f4 <- function(n) { x <- numeric(n) y <- character(n) for (i in 1:n) { x[i] <- i y[i] <- i } data.frame(x, y, stringsAsFactors=FALSE) } 

microbenchmark del paquete "microbenchmark" nos dará una visión más completa que system.time :

 library(microbenchmark) microbenchmark(f1(1000), f3(1000), f4(1000), times = 5) # Unit: milliseconds # expr min lq median uq max neval # f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5 # f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5 # f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5 

f1() (el enfoque a continuación) es increíblemente ineficiente debido a la frecuencia con que se llama data.frame y porque el crecimiento de objetos de esa manera es generalmente lento en R. f3() es mucho mejor debido a la data.frame , pero la estructura de data.frame sí misma ser parte del cuello de botella aquí. f4() intenta eludir ese cuello de botella sin comprometer el enfoque que desea tomar.


Respuesta original

Esto realmente no es una buena idea, pero si quisieras hacerlo de esta manera, supongo que puedes intentarlo:

 for (i in 1:10) { df <- rbind(df, data.frame(x = i, y = toString(i))) } 

Tenga en cuenta que en su código, hay otro problema:

  • Debería usar stringsAsFactors si desea que los caracteres no se conviertan en factores. Uso: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

Comparemos las tres soluciones propuestas:

 # use rbind f1 <- function(n){ df <- data.frame(x = numeric(), y = character()) for(i in 1:n){ df <- rbind(df, data.frame(x = i, y = toString(i))) } df } # use list f2 <- function(n){ df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE) for(i in 1:n){ df[i,] <- list(i, toString(i)) } df } # pre-allocate space f3 <- function(n){ df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE) for(i in 1:n){ df$x[i] <- i df$y[i] <- toString(i) } df } system.time(f1(1000)) # user system elapsed # 1.33 0.00 1.32 system.time(f2(1000)) # user system elapsed # 0.19 0.00 0.19 system.time(f3(1000)) # user system elapsed # 0.14 0.00 0.14 

La mejor solución es preasignar el espacio (como se pretende en R). La siguiente mejor solución es usar la list , y la peor solución (al menos en función de estos resultados de tiempo) parece ser la de rbind .

Supongamos que simplemente no conoce el tamaño del data.frame por adelantado. Puede ser unas pocas filas, o algunos millones. Necesita tener algún tipo de contenedor, que crezca dinámicamente. Tomando en consideración mi experiencia y todas las respuestas relacionadas en SO, vengo con 4 soluciones distintas:

  1. rbindlist a data.frame

  2. Usa la operación de set rápida de data.table y data.table con la duplicación manual de la tabla cuando sea necesario.

  3. Use RSQLite y anexe a la tabla RSQLite en la memoria.

  4. La capacidad de data.frame para crecer y usar un entorno personalizado (que tiene semántica de referencia) para almacenar el data.frame para que no se copie a la vuelta.

Aquí hay una prueba de todos los métodos para una gran cantidad de filas adjuntas. Cada método tiene 3 funciones asociadas a él:

  • 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 el object ).

  • access(object) obtiene el data.frame con todos los elementos insertados.

rbindlist a data.frame

Eso es bastante fácil y directo:

 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 tabla cuando sea necesario.

Almacenaré la verdadera longitud de la tabla en un atributo 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 debe optimizarse para la inserción rápida de registros, por lo que inicialmente tenía grandes esperanzas para la solución RSQLite

Esto es básicamente copiar y pegar de Karsten W. responder 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)) } 

entorno personal personalizado de fila de datos de 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) } 

El conjunto de pruebas:

Para mayor comodidad, usaré una función de prueba para cubrirlos todos con llamadas indirectas. (Lo comprobé: el uso de do.call lugar de llamar a las funciones directamente no hace que el código se pueda medir 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 el 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) 

Tiempos para agregar n = 10 filas

Tiempos para n = 100 filas Tiempos para n = 1000 filas

Para filas 1E5 (mediciones realizadas en Intel (R) Core (TM) i7-4710HQ CPU 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 el sulution basado en SQLite, aunque recupera algo de velocidad en datos grandes, no se acerca al crecimiento exponencial manual de data.table +. ¡La diferencia es casi dos órdenes de magnitud!

Resumen

Si sabe que va a 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 de corchetes e ignore el hecho de que data.frame es no pre-poblado

Para todo lo demás, use data.table::set y haga crecer data.table exponencialmente (por ejemplo, usando mi código).

Tomemos un vector ‘punto’ que tiene números del 1 al 5

point = c(1,2,3,4,5)

si queremos agregar un número 6 en cualquier lugar dentro del vector, entonces el comando debajo puede ser útil

i) Vectores

new_var = append(point, 6 ,after = length(point))

ii) columnas de una mesa

new_var = append(point, 6 ,after = length(mtcars$mpg))

El comando append toma tres argumentos:

  1. el vector / columna a ser modificado.
  2. valor que se incluirá en el vector modificado.
  3. un subíndice, después del cual los valores deben ser anexados.

sencillo…!! Disculpas en caso de …

Una solución más genérica para podría ser la siguiente.

  extendDf <- function (df, n) { withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0 nr <- nrow (df) colNames <- names(df) for (c in 1:length(colNames)) { if (is.factor(df[,c])) { col <- vector (mode='character', length = nr+n) col[1:nr] <- as.character(df[,c]) col[(nr+1):(n+nr)]<- rep(col[1], n) # to avoid extra levels col <- as.factor(col) } else { col <- vector (mode=mode(df[1,c]), length = nr+n) class(col) <- class (df[1,c]) col[1:nr] <- df[,c] } if (c==1) { newDf <- data.frame (col ,stringsAsFactors=withFactors) } else { newDf[,c] <- col } } names(newDf) <- colNames newDf } 

La función extendDf () extiende un dataframe con n filas.

Como ejemplo:

 aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE) extendDf (aDf, 2) # linct # 1 TRUE 1 1 a 2016-07-06 17:12:30 # 2 FALSE 0 0 a 1970-01-01 01:00:00 # 3 FALSE 0 0 a 1970-01-01 01:00:00 system.time (eDf <- extendDf (aDf, 100000)) # user system elapsed # 0.009 0.002 0.010 system.time (eDf <- extendDf (eDf, 100000)) # user system elapsed # 0.068 0.002 0.070 
    Intereting Posts