68 votes

Méthode correcte/rapide pour remodeler un tableau de données (data.table)

J'ai un tableau de données en 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 tableau 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 que les groupes (y) soient présentés en colonnes, plutôt qu'en lignes. Je peux y parvenir 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 de data.table ?

75voto

Zach Points 6592

El data.table Le paquet met en œuvre un système plus rapide melt/dcast fonctions (en C). Il dispose également de fonctionnalités supplémentaires en permettant de fondre et de couler plusieurs colonnes . Veuillez consulter le nouveau Remodelage efficace à l'aide de data.tables sur Github.

Les fonctions melt/dcast pour data.table sont disponibles depuis la v1.9.0 et les caractéristiques sont les suivantes :

  • Il n'est pas nécessaire de charger reshape2 avant le moulage. Mais si vous voulez le charger pour d'autres opérations, veuillez le charger avant chargement data.table .

  • dcast est également un générique S3. Pas plus dcast.data.table() . Il suffit d'utiliser dcast() .

  • melt :

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

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

    • melt.data.table 's na.rm = TRUE est optimisé en interne pour éliminer 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 facilité par l'utilisation de la fonction patterns() . Voir vignette ou ?melt .

  • dcast :

    • accepte de multiples fun.aggregate et de multiples value.var . Voir vignette ou ?dcast .

    • utiliser rowid() directement dans la formule pour générer une colonne d'identification, 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 secondes.
    • dcast : 1 million de lignes et 4 colonnes, 192 secondes réduites à 3,6 secondes.

Rappel de la diapositive 32 de la présentation de Cologne (déc 2013) : Pourquoi ne pas soumettre un dcast demande de téléchargement à reshape2 ?

32voto

Christoph_J Points 2402

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 cet excellent morceau de code d'Arun. ici sur SO . Donc je suppose qu'il y a un data.table solution. Appliquée à 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

De plus, il 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 

UPDATE

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 la table 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 en une 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 simple 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

21voto

BondedDust Points 105234

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

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

7voto

Ramnath Points 24798

Vous pouvez utiliser dcast 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")

NOTE : Le tapply serait beaucoup plus rapide.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X