Creación de un marco de datos R fila por fila

107

Me gustaría construir un marco de datos fila por fila en R.He hecho algunas búsquedas y todo lo que se me ocurrió es la sugerencia de crear una lista vacía, mantener un índice de lista escalar y luego agregarlo cada vez a la lista. un marco de datos de una sola fila y avance el índice de la lista en uno. Finalmente, do.call(rbind,)en la lista.

Si bien esto funciona, parece muy engorroso. ¿No hay una forma más fácil de lograr el mismo objetivo?

Obviamente, me refiero a casos en los que no puedo usar alguna applyfunción y explícitamente necesito crear el marco de datos fila por fila. Al menos, ¿hay alguna manera de llegar pushal final de una lista en lugar de realizar un seguimiento explícito del último índice utilizado?

David B
fuente
1
Puede usar append()[que probablemente debería llamarse insertar] o c()agregar elementos al final de una lista, aunque no lo ayudará aquí.
hatmatrix
No hay muchas funciones en R que las tramas de datos de vuelta a menos que devolverlos [modo de fila] a partir de lapply(), Map()y así sucesivamente, pero también es posible que desee echar un vistazo a aggregate(), dapply() {heR.Misc}y cast() {reshape}para ver si sus tareas no pueden ser manejados por éstos funciones (todas devuelven marcos de datos).
hatmatrix

Respuestas:

96

Puede hacerlos crecer fila por fila agregando o usando rbind().

Eso no significa que debas hacerlo. Las estructuras de crecimiento dinámico es una de las formas menos eficientes de codificar en R.

Si puede, asigne todo su data.frame por adelantado:

N <- 1e4  # total number of rows to preallocate--possibly an overestimate

DF <- data.frame(num=rep(NA, N), txt=rep("", N),  # as many cols as you need
                 stringsAsFactors=FALSE)          # you don't know levels yet

y luego, durante sus operaciones, inserte una fila a la vez

DF[i, ] <- list(1.4, "foo")

Eso debería funcionar para data.frame arbitrario y ser mucho más eficiente. Si sobrepasa N, siempre puede reducir las filas vacías al final.

Dirk Eddelbuettel
fuente
6
¿No quiso poner N en lugar de 10, y listar (1.4, "foo") en lugar de c (1.4, "foo") para no coaccionar al 1.4 en modo carácter?
hatmatrix
Sí, quise usar N en la creación de data.frame. Además, muy bien la coacción en el chat: me lo había perdido.
Dirk Eddelbuettel
1
Sería mejor editar la respuesta que dejarla en los comentarios. Estaba confundido tratando de asimilar esta respuesta.
Usuario
4
data.tableparece ser incluso más rápido que la preasignación utilizando data.frames. Probando aquí: stackoverflow.com/a/11486400/636656
Ari B. Friedman
¿Esto sigue siendo cierto en R 3.1 donde debería ser más rápido?
userJT
49

Se pueden agregar filas a NULL:

df<-NULL;
while(...){
  #Some code that generates new row
  rbind(df,row)->df
}

por ejemplo

df<-NULL
for(e in 1:10) rbind(df,data.frame(x=e,square=e^2,even=factor(e%%2==0)))->df
print(df)
mbq
fuente
3
genera una matriz, no un marco de datos
Olga
1
@Olga Solo si enlaza filas de elementos de un tipo igual; por cierto, en ese caso es mejor sapply(o vectorizar) y transponer.
mbq
1
@mbq Exactamente lo que estoy haciendo. También encontré que si lo inicializa con df <-data.frame (), genera un marco de datos.
Olga
9

Este es un ejemplo tonto de cómo usar do.call(rbind,)en la salida de Map()[que es similar a lapply()]

> DF <- do.call(rbind,Map(function(x) data.frame(a=x,b=x+1),x=1:3))
> DF
  x y
1 1 2
2 2 3
3 3 4
> class(DF)
[1] "data.frame"

Utilizo esta construcción con bastante frecuencia.

hatmatrix
fuente
8

La razón por la que me gusta tanto Rcpp es que no siempre entiendo cómo piensa R Core, y con Rcpp, la mayoría de las veces, no tengo que hacerlo.

Hablando filosóficamente, estás en un estado de pecado con respecto al paradigma funcional, que trata de asegurar que cada valor parezca independiente de cualquier otro valor; cambiar un valor nunca debería provocar un cambio visible en otro valor, como ocurre con los punteros que comparten la representación en C.

Los problemas surgen cuando la programación funcional le indica a la pequeña nave que se mueva fuera del camino, y la pequeña nave responde "Soy un faro". Hacer una larga serie de pequeños cambios en un objeto grande que desea procesar mientras tanto lo coloca en el territorio del faro.

En C ++ STL, push_back()es una forma de vida. No intenta ser funcional, pero intenta adaptarse a los modismos comunes de programación de manera eficiente. .

Con un poco de inteligencia entre bastidores, a veces puede hacer arreglos para tener un pie en cada mundo. Los sistemas de archivos basados ​​en instantáneas son un buen ejemplo (que evolucionó a partir de conceptos como los montajes de unión, que también cubren ambos lados).

Si R Core quisiera hacer esto, el almacenamiento vectorial subyacente podría funcionar como un montaje de unión. Una referencia al almacenamiento de vectores puede ser válida para subíndices 1:N, mientras que otra referencia al mismo almacenamiento es válida para subíndices 1:(N+1). Podría haber un almacenamiento reservado que aún no esté referenciado de manera válida por nada más que conveniente para un archivo push_back(). No viola el concepto funcional al agregar fuera del rango que cualquier referencia existente considera válida.

Al agregar filas de forma incremental, te quedas sin almacenamiento reservado. Deberá crear nuevas copias de todo, con el almacenamiento multiplicado por algún incremento. Las implementaciones de STL que utilizo tienden a multiplicar el almacenamiento por 2 al extender la asignación. Pensé haber leído en R Internals que hay una estructura de memoria donde el almacenamiento se incrementa en un 20%. De cualquier manera, las operaciones de crecimiento ocurren con frecuencia logarítmica en relación con el número total de elementos agregados. Sobre una base amortizada, esto suele ser aceptable.

En cuanto a los trucos entre bastidores, he visto peores. Cada vez que push_back()ingrese una nueva fila en el marco de datos, se deberá copiar una estructura de índice de nivel superior. La nueva fila podría agregarse a la representación compartida sin afectar ningún valor funcional anterior. Ni siquiera creo que complicaría mucho al recolector de basura; ya que no propongo que push_front()todas las referencias sean referencias de prefijo al frente del almacenamiento de vectores asignado.

Allan Stokes
fuente
2

La respuesta de Dirk Eddelbuettel es la mejor; aquí solo noto que puede salirse con la suya sin especificar previamente las dimensiones del marco de datos o los tipos de datos, lo que a veces es útil si tiene varios tipos de datos y muchas columnas:

row1<-list("a",1,FALSE) #use 'list', not 'c' or 'cbind'!
row2<-list("b",2,TRUE)  

df<-data.frame(row1,stringsAsFactors = F) #first row
df<-rbind(df,row2) #now this works as you'd expect.
Juan
fuente
¿Quiso decir df<-rbind(df, row2)?
Timothy C. Quinn
1

Encontré esta forma de crear un marco de datos sin formato sin matriz.

Con nombre de columna automático

df<-data.frame(
        t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
        ,row.names = NULL,stringsAsFactors = FALSE
    )

Con nombre de columna

df<-setNames(
        data.frame(
            t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
            ,row.names = NULL,stringsAsFactors = FALSE
        ), 
        c("col1","col2","col3")
    )
phili_b
fuente
0

Si tiene vectores destinados a convertirse en filas, concatenarlos usando c(), pasarlos a una matriz fila por fila y convertir esa matriz en un marco de datos.

Por ejemplo, filas

dummydata1=c(2002,10,1,12.00,101,426340.0,4411238.0,3598.0,0.92,57.77,4.80,238.29,-9.9)
dummydata2=c(2002,10,2,12.00,101,426340.0,4411238.0,3598.0,-3.02,78.77,-9999.00,-99.0,-9.9)
dummydata3=c(2002,10,8,12.00,101,426340.0,4411238.0,3598.0,-5.02,88.77,-9999.00,-99.0,-9.9)

se puede convertir a un marco de datos así:

dummyset=c(dummydata1,dummydata2,dummydata3)
col.len=length(dummydata1)
dummytable=data.frame(matrix(data=dummyset,ncol=col.len,byrow=TRUE))

Es cierto que veo 2 limitaciones principales: (1) esto solo funciona con datos monomodo y (2) debe conocer sus # columnas finales para que esto funcione (es decir, supongo que no está trabajando con un matriz irregular cuya mayor longitud de fila se desconoce a priori ).

Esta solución parece simple, pero según mi experiencia con las conversiones de tipos en R, estoy seguro de que crea nuevos desafíos en el futuro. ¿Alguien puede comentar sobre esto?

Keegan Smith
fuente
0

Dependiendo del formato de su nueva fila, puede usar tibble::add_rowsi su nueva fila es simple y se puede especificar en "pares de valores". O podría usar dplyr::bind_rows"una implementación eficiente del patrón común de do.call (rbind, dfs)".

Arthur Yip
fuente