Faire fondre un tableau dans data.frame mais en convertissant une dimension en colonnes
Question
Je voudrais convertir un tableau à plusieurs dimensions (par ex.x, y, z ;voir 'arr' ci-dessous) dans un data.frame, mais conservez une dimension dans les colonnes (par ex.z, voir 'df2' ci-dessous).
Actuellement, j'utilise les fonctions melt et dcast dans le package 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
Cependant, il a fallu environ 10 s pour convertir 5 M valeurs.
user system elapsed
8.13 1.11 9.39
Existe-t-il des méthodes plus efficaces ?Merci pour toutes suggestions.
La solution
Voici un légèrement solution plus généralisée pour un tableau à 4 dimensions utilisant une combinaison de aperm(...)
et matrix(...)
.Je ne suis pas assez sorcier pour généraliser davantage.
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)))
Il s'agit de votre méthode existante qui utilise melt(...)
et dcast(...)
pour supprimer toutes les dimensions sauf la dernière :
f.dcast <- function(a) dcast(melt(a), x + y + z ~ w)
Ce qui suit fait la même chose en utilisant aperm(...)
pour écrire les données sous forme de vecteur dans un ordre particulier afin qu'elles se transforment en une matrice correctement formatée, puis cbind
s avec les noms de 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)
}
Ils me donnent tous les deux le même résultat :
> desired <- f.dcast(original)
> test <- f.aperm(original)
> all.equal(desired, test)
[1] TRUE
La deuxième méthode est 6 fois plus rapide pour un tableau de cette taille :
> 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 j'augmente la taille du tableau d'origine :
nx <- 10 ; ny <- 20 ; nz <- 30 ; nw <- 40
Ensuite, la deuxième méthode est plus de dix fois plus rapide :
> 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
Autres conseils
cbind(x=rep(1:1000,each=1000),
y=1:1000,
matrix(arr, ncol=5, dimnames=list(list(),dimnames(arr)$z) ) ))
temps écoulé pour c'était autour d'un dixième de seconde.C'était le résultat 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" ..
Je suppose que vous pourriez mettre en rangée. Noms, bien qu'il augmente le temps écoulé à un peu plus d'une seconde.