web-dev-qa-db-fra.com

Moyen approprié / rapide de remodeler une table de données.

J'ai un tableau de données dans R:

library(data.table)
set.seed(1234)
DT <- data.table(x=rep(c(1,2,3),each=4), y=c("A","B"), v=sample(1:100,12))
DT
      x y  v
 [1,] 1 A 12
 [2,] 1 B 62
 [3,] 1 A 60
 [4,] 1 B 61
 [5,] 2 A 83
 [6,] 2 B 97
 [7,] 2 A  1
 [8,] 2 B 22
 [9,] 3 A 99
[10,] 3 B 47
[11,] 3 A 63
[12,] 3 B 49

Je peux facilement additionner la variable v par les groupes dans le data.table:

out <- DT[,list(SUM=sum(v)),by=list(x,y)]
out
     x  y SUM
[1,] 1 A  72
[2,] 1 B 123
[3,] 2 A  84
[4,] 2 B 119
[5,] 3 A 162
[6,] 3 B  96

Cependant, j'aimerais avoir les groupes (y) sous forme de colonnes, plutôt que de lignes. Je peux accomplir cela en utilisant reshape:

out <- reshape(out,direction='wide',idvar='x', timevar='y')
out
     x SUM.A SUM.B
[1,] 1    72   123
[2,] 2    84   119
[3,] 3   162    96

Existe-t-il un moyen plus efficace de remodeler les données après les avoir agrégées? Existe-t-il un moyen de combiner ces opérations en une seule étape, en utilisant les opérations data.table?

66
Zach

Le package data.table Implémente des fonctions melt/dcast Plus rapides (en C). Il a également des fonctionnalités supplémentaires en permettant de fondre et de couler plusieurs colonnes . Veuillez voir le nouveau Remodelage efficace en utilisant data.tables sur Github.

les fonctions de fusion/diffusion pour data.table sont disponibles depuis la v1.9.0 et les fonctionnalités incluent:

  • Il n'est pas nécessaire de charger le package reshape2 Avant le casting. Mais si vous voulez qu'il soit chargé pour d'autres opérations, veuillez le charger avant de charger data.table.

  • dcast est également un générique S3. Plus de dcast.data.table(). Utilisez simplement dcast().

  • melt:

    • est capable de fondre sur des colonnes de type 'liste'.

    • gagne variable.factor et value.factor qui sont par défaut TRUE et FALSE respectivement pour la compatibilité avec reshape2. Cela permet de contrôler directement le type de sortie des colonnes variable et value (en tant que facteurs ou non).

    • Le paramètre melt.data.table De na.rm = TRUE Est optimisé en interne pour supprimer les NA directement pendant la fusion et est donc beaucoup plus efficace.

    • NOUVEAU: melt peut accepter une liste pour measure.vars Et les colonnes spécifiées dans chaque élément de la liste seront combinées ensemble. Ceci est encore facilité par l'utilisation de patterns(). Voir la vignette ou ?melt.

  • dcast:

    • accepte plusieurs fun.aggregate et plusieurs value.var. Voir la vignette ou ?dcast.

    • utilisez la fonction rowid() directement dans la formule pour générer une colonne id, qui est parfois nécessaire pour identifier les lignes de manière unique. Voir? Dcast.

  • Anciens repères:

    • melt: 10 millions de lignes et 5 colonnes, 61,3 secondes réduites à 1,2 seconde.
    • dcast: 1 million de lignes et 4 colonnes, 192 secondes réduites à 3,6 secondes.

Rappel de la présentation de Cologne (décembre 2013) diapositive 32: Pourquoi ne pas soumettre une demande de tirage dcast à reshape2?

73
Zach

Cette fonctionnalité est maintenant implémentée dans data.table (à partir de la version 1.8.11), comme on peut le voir dans la réponse de Zach ci-dessus.

Je viens de voir ce grand morceau de code d'Arun ici sur SO . Donc je suppose qu'il y a un data.table Solution. Appliqué à ce problème:

library(data.table)
set.seed(1234)
DT <- data.table(x=rep(c(1,2,3),each=1e6), 
                  y=c("A","B"), 
                  v=sample(1:100,12))

out <- DT[,list(SUM=sum(v)),by=list(x,y)]
# edit (mnel) to avoid setNames which creates a copy
# when calling `names<-` inside the function
out[, as.list(setattr(SUM, 'names', y)), by=list(x)]
})
   x        A        B
1: 1 26499966 28166677
2: 2 26499978 28166673
3: 3 26500056 28166650

Cela donne les mêmes résultats que l'approche de DWin:

tapply(DT$v,list(DT$x, DT$y), FUN=sum)
         A        B
1 26499966 28166677
2 26499978 28166673
3 26500056 28166650

Aussi, c'est rapide:

system.time({ 
   out <- DT[,list(SUM=sum(v)),by=list(x,y)]
   out[, as.list(setattr(SUM, 'names', y)), by=list(x)]})
##  user  system elapsed 
## 0.64    0.05    0.70 
system.time(tapply(DT$v,list(DT$x, DT$y), FUN=sum))
## user  system elapsed 
## 7.23    0.16    7.39 

[~ # ~] mise à jour [~ # ~]

Pour que cette solution fonctionne également pour les ensembles de données non équilibrés (c'est-à-dire que certaines combinaisons n'existent pas), vous devez d'abord les saisir dans le tableau de données:

library(data.table)
set.seed(1234)
DT <- data.table(x=c(rep(c(1,2,3),each=4),3,4), y=c("A","B"), v=sample(1:100,14))

out <- DT[,list(SUM=sum(v)),by=list(x,y)]
setkey(out, x, y)

intDT <- expand.grid(unique(out[,x]), unique(out[,y]))
setnames(intDT, c("x", "y"))
out <- out[intDT]

out[, as.list(setattr(SUM, 'names', y)), by=list(x)]

Résumé

En combinant les commentaires avec ce qui précède, voici la solution à 1 ligne:

DT[, sum(v), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
   setNames(as.list(V1), paste(y)), by = x]

Il est également facile de modifier cela pour avoir plus que la somme, par exemple:

DT[, list(sum(v), mean(v)), keyby = list(x,y)][CJ(unique(x), unique(y)), allow.cartesian = T][,
   setNames(as.list(c(V1, V2)), c(paste0(y,".sum"), paste0(y,".mean"))), by = x]
#   x A.sum B.sum   A.mean B.mean
#1: 1    72   123 36.00000   61.5
#2: 2    84   119 42.00000   59.5
#3: 3   187    96 62.33333   48.0
#4: 4    NA    81       NA   81.0
32
Christoph_J

Les objets Data.table héritent de 'data.frame', vous pouvez donc simplement utiliser tapply:

> tapply(DT$v,list(DT$x, DT$y), FUN=sum)
   AA  BB
a  72 123
b  84 119
c 162  96
21
42-

Vous pouvez utiliser dcast à partir de reshape2 bibliothèque. Voici le code

# DUMMY DATA
library(data.table)
mydf = data.table(
  x = rep(1:3, each = 4),
  y = rep(c('A', 'B'), times = 2),
  v = rpois(12, 30)
)

# USE RESHAPE2
library(reshape2)
dcast(mydf, x ~ y, fun = sum, value_var = "v")

REMARQUE: la solution tapply serait beaucoup plus rapide.

7
Ramnath