La lista más eficiente para el método data.frame?

Acabo de tener una conversación con compañeros de trabajo sobre esto, y pensamos que valdría la pena ver lo que la gente de SO tenía que decir. Supongamos que tengo una lista con N elementos, donde cada elemento es un vector de longitud X. Supongamos ahora que quería transformar eso en un data.frame. Al igual que con la mayoría de las cosas en R, hay varias formas de despellejar al gato proverbial, como as.dataframe , utilizando el paquete plyr, combinando do.call con cbind , cbind previamente el DF y llenándolo, y otros.

El problema que se presentó fue lo que sucede cuando N o X (en nuestro caso es X) se vuelve extremadamente grande. ¿Existe un método de desollado de un gato que sea notablemente superior cuando la eficiencia (especialmente en términos de memoria) es esencial?

Como un data.frame ya es una lista y usted sabe que cada elemento de la lista tiene la misma longitud (X), lo más rápido probablemente sería actualizar los atributos class y row.names :

 set.seed(21) n <- 1e6 x <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) x <- c(x,x,x,x,x,x) system.time(a <- as.data.frame(x)) system.time(b <- do.call(data.frame,x)) system.time({ d <- x # Skip 'c' so Joris doesn't down-vote me! ;-) class(d) <- "data.frame" rownames(d) <- 1:n names(d) <- make.unique(names(d)) }) identical(a, b) # TRUE identical(b, d) # TRUE 

Actualización : esto es ~ 2 veces más rápido que crear d :

 system.time({ e <- x attr(e, "row.names") <- c(NA_integer_,n) attr(e, "class") <- "data.frame" attr(e, "names") <- make.names(names(e), unique=TRUE) }) identical(d, e) # TRUE 

Actualización 2 - Olvidé el consumo de memoria. La última actualización hace dos copias de e . El uso de la función de attributes reduce a una sola copia.

 set.seed(21) f <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) f <- c(f,f,f,f,f,f) tracemem(f) system.time({ # makes 2 copies attr(f, "row.names") <- c(NA_integer_,n) attr(f, "class") <- "data.frame" attr(f, "names") <- make.names(names(f), unique=TRUE) }) set.seed(21) g <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) g <- c(g,g,g,g,g,g) tracemem(g) system.time({ # only makes 1 copy attributes(g) <- list(row.names=c(NA_integer_,n), class="data.frame", names=make.names(names(g), unique=TRUE)) }) identical(f,g) # TRUE 

Esto parece necesitar una sugerencia de data.table dado que se requiere eficiencia para grandes conjuntos de datos. En particular, setattr establece por referencia y no copia

 library(data.table) set.seed(21) n <- 1e6 h <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) h <- c(h,h,h,h,h,h) tracemem(h) system.time({h <- as.data.table(h) setattr(h, 'names', make.names(names(h), unique=T))}) 

as.data.table , sin embargo, hace una copia.


Editar - sin copia

Usando la sugerencia de @ MatthewDowle setattr(h,'class','data.frame') que se convertirá en data.frame por referencia ( sin copias )

 set.seed(21) n <- 1e6 i <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) i <- c(i,i,i,i,i,i) tracemem(i) system.time({ setattr(i, 'class', 'data.frame') setattr(i, "row.names", c(NA_integer_,n)) setattr(i, "names", make.names(names(i), unique=TRUE)) })