performance - La lista más eficiente para el método data.frame?
memory-management dataframe (2)
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))
})