Fusionar una matriz en data.frame pero convirtiendo una dimensión en columnas
Pregunta
Me gustaría convertir una matriz con múltiples dimensiones (p. ej.x, y, z;ver 'arr' a continuación) en un data.frame, pero mantenga una dimensión en las columnas (p. ej.z, consulte 'df2' a continuación).
Actualmente, uso las funciones melt y dcast en el paquete reshape2.
set.seed(1111)
num <- 1000
dim_names <- list(x = seq(num), y = seq(num),
z = paste0('Z', 1:5))
dim_arr <- as.numeric(lapply(dim_names, length))
arr <- array(runif(prod(dim_arr)), dim = dim_arr)
dimnames(arr) <- dim_names
library(reshape2)
df <- melt(arr)
head(df)
system.time(df2 <- dcast(df, x + y ~ z, value.var = 'value'))
head(df2)
x y Z1 Z2 Z3 Z4 Z5
1 1 1 0.4655026 0.8027921 0.1950717 0.0403759 0.04669389
2 1 2 0.5156263 0.5427343 0.5799924 0.1911539 0.26069063
3 1 3 0.2788747 0.9394142 0.9081274 0.7712205 0.68748300
4 1 4 0.2827058 0.8001632 0.6995503 0.9913805 0.25421346
5 1 5 0.7054767 0.8013255 0.2511769 0.6556174 0.07780849
6 1 6 0.5576141 0.6452644 0.3362980 0.7353494 0.93147223
Sin embargo, se necesitaron unos 10 s para convertir valores de 5 M.
user system elapsed
8.13 1.11 9.39
¿Existen métodos más eficientes?Gracias por cualquier sugerencia.
Solución
Aquí está un levemente solución más generalizada para una matriz de 4 dimensiones usando una combinación de aperm(...)
y matrix(...)
.No soy lo suficientemente mago como para generalizar esto más.
nx <- 2 ; ny <- 3 ; nz <- 4 ; nw <- 5
original <- array(rnorm(nx*ny*nz*nw), dim=c(nx,ny,nz,nw),
dimnames=list(x=sprintf('x%s', 1:nx), y=sprintf('y%s', 1:ny),
z=sprintf('z%s', 1:nz), w=sprintf('w%s', 1:nw)))
Este es su método existente que utiliza melt(...)
y dcast(...)
para eliminar todas menos la última dimensión:
f.dcast <- function(a) dcast(melt(a), x + y + z ~ w)
Lo siguiente hace lo mismo usando aperm(...)
escribir los datos como un vector en un orden particular para que terminen como una matriz con el formato adecuado, luego cbind
s con los nombres de las variables:
f.aperm <- function(a) {
d <- dim(a)
data <- matrix(as.vector(aperm(a, c(4,3,2,1))), ncol=d[4], byrow=T)
colnames(data) <- dimnames(a)[[4]]
# specify levels in the same order as the input so they don't wind up alphabetical
varnames <- data.frame(
factor(rep(dimnames(a)[[1]], times=1, each=d[2]*d[3]), levels=dimnames(a)[[1]]),
factor(rep(dimnames(a)[[2]], times=d[1], each=d[3] ), levels=dimnames(a)[[2]]),
factor(rep(dimnames(a)[[3]], times=d[1]*d[2], each=1 ), levels=dimnames(a)[[3]])
)
names(varnames) <- names(dimnames(a))[1:3]
cbind(varnames, data)
}
Ambos me dan el mismo resultado:
> desired <- f.dcast(original)
> test <- f.aperm(original)
> all.equal(desired, test)
[1] TRUE
El segundo método es 6 veces más rápido para una matriz de este tamaño:
> microbenchmark::microbenchmark(f.dcast(original), f.aperm(original))
Unit: milliseconds
expr min lq mean median uq max neval
f.dcast(original) 7.270208 7.343227 7.703360 7.481984 7.698812 10.392141 100
f.aperm(original) 1.218162 1.244595 1.327204 1.259987 1.291986 4.182391 100
Si aumento el tamaño de la matriz original:
nx <- 10 ; ny <- 20 ; nz <- 30 ; nw <- 40
Entonces el segundo método es diez veces más rápido:
> microbenchmark::microbenchmark(f.dcast(original), f.aperm(original))
Unit: milliseconds
expr min lq mean median uq max neval
f.dcast(original) 303.21812 385.44857 381.29150 392.11693 394.81721 472.80343 100
f.aperm(original) 18.62788 22.25814 28.85363 23.90133 24.54939 97.96776 100
Otros consejos
cbind(x=rep(1:1000,each=1000),
y=1:1000,
matrix(arr, ncol=5, dimnames=list(list(),dimnames(arr)$z) ) ))
Tiempo transcurrido para eso fue alrededor de una décima de segundo.Este fue el resultado de STR ()
num [1:1000000, 1:7] 1 1 1 1 1 1 1 1 1 1 ...
- attr(*, "dimnames")=List of 2
..$ : NULL
..$ : chr [1:7] "x" "y" "Z1" "Z2" ..
Supongo que podría poner en fila.names, aunque aumenta el tiempo transcurrido a un poco durante un segundo.