2011-05-09 10 views
29

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 en tierra 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 transformarlo en un data.frame. Al igual que con la mayoría de las cosas en R, hay varias maneras de despellejar el gato proverbial, como as.dataframe, utilizando el paquete plyr, que combina do.call con cbind, preasignar el DF y rellenarlo, y otros.¿La lista más eficiente para el método data.frame?

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 notablemente superior cuando la eficiencia (especialmente en términos de memoria) es esencial?

Respuesta

26

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

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 la creación de 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 - I de Obtuve el consumo de memoria. La última actualización hace dos copias de e. El uso de la función attributes reduce eso a solo una 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 
+2

Dejar "probablemente" fuera de la respuesta y es correcto También es correcto si realiza una función utilizando esas llamadas y reemplaza el truco de saber n con un comando de longitud. Su nueva función es más o menos equivalente a data.frame() después de eliminar todas las comprobaciones exhaustivas. Por lo tanto, si está seguro de que está transfiriendo la llamada a la entrada correcta, simplemente haga lo que Josh recomendó para la velocidad. Si no está seguro, data.frame es más seguro y, do.call (data.frame, x)) es el siguiente más rápido (por extraño que parezca). – John

+2

Ver 'plyr :: quickdf' para exactamente esta función. – hadley

+0

@hadley: 'plyr :: quickdf' no proporciona exactamente esta función; a saber, no hace nombres de columnas únicos. 'plyr ::: make_names' solo reemplaza los nombres que faltan y no tiene un' unique = 'arg como' base :: make.names'. –

10

Esto parece necesitar una sugerencia data.table dado que se requiere eficiencia para grandes conjuntos de datos. Cabe destacar que setattr conjuntos de 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 - no hay una versión de copia

Usando @ sugerencia setattr(h,'class','data.frame') que convertirá a hoja.de.datos por referencia (ninguna copia) de MatthewDowle

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)) 

}) 
+1

setattr (h, "clase", "data.frame") debe ser instantáneo, sin copia. –

+0

@MatthewDowle - Como es 'setattr (h," clase "," data.table ")';) (Muy bueno, por cierto). –

+0

@ JoshO'Brien Indeed :) Solo me di cuenta en los últimos días que '? Setattr' dice que' x' debe ser 'data.table' (gracias a comment en datatable-help). 'setattr' en realidad está destinado a funcionar en cualquier cosa. Arreglará el docu. Devuelve su entrada también, por lo que puede componer '[i, j, by]' después si es necesario (digamos si lo envuelve en un alias: 'setDT (DF) [i, j, by]'). –

Cuestiones relacionadas