Fusionar una matriz en data.frame pero convirtiendo una dimensión en columnas

StackOverflow https://stackoverflow.com//questions/22003121

  •  20-12-2019
  •  | 
  •  

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.

¿Fue útil?

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 cbinds 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top