Faire fondre un tableau dans data.frame mais en convertissant une dimension en colonnes

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

  •  20-12-2019
  •  | 
  •  

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.

Était-ce utile?

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

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top