Derreta uma matriz em data.frame, mas convertendo uma dimensão em colunas

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

  •  20-12-2019
  •  | 
  •  

Pergunta

Gostaria de converter uma matriz com múltiplas dimensões (por exemplo,x, y, z;veja 'arr' abaixo) em um data.frame, mas mantenha uma dimensão nas colunas (por exemploz, veja 'df2' abaixo).

Atualmente, uso funções melt e dcast no pacote 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

No entanto, demorou cerca de 10 s para converter valores de 5 M

  user  system elapsed 
  8.13    1.11    9.39 

Existem métodos mais eficientes?Obrigado por qualquer sugestão.

Foi útil?

Solução

Aqui está um um pouco solução mais generalizada para uma matriz quadridimensional usando uma combinação de aperm(...) e matrix(...).Não sou mago o suficiente para generalizar isso ainda mais.

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 é o seu método existente que usa melt(...) e dcast(...) para remover todas, exceto a última dimensão:

f.dcast <- function(a) dcast(melt(a), x + y + z ~ w)

O seguinte faz a mesma coisa usando aperm(...) escrever os dados como um vetor em uma ordem específica para que acabem como uma matriz formatada corretamente, então cbinds com os nomes das variáveis:

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 dão o mesmo resultado:

> desired <- f.dcast(original)
> test <- f.aperm(original)
> all.equal(desired, test)
[1] TRUE

O segundo método é 6 vezes mais rápido para um array deste tamanho:

> 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

Se eu aumentar o tamanho do array original:

nx <- 10 ; ny <- 20 ; nz <- 30 ; nw <- 40

Então o segundo método é dez vezes mais 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

Outras dicas

cbind(x=rep(1:1000,each=1000), 
      y=1:1000, 
      matrix(arr, ncol=5, dimnames=list(list(),dimnames(arr)$z) ) ))

O tempo decorrido para isso foi de cerca de um décimo de segundo.Este foi o 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" ..

Suponho que você possa inserir row.names, embora isso aumente o tempo decorrido para um pouco mais de um segundo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top