Derreta uma matriz em data.frame, mas convertendo uma dimensão em colunas
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.
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 cbind
s 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.