Moyen correct / rapide de remodeler une 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 xyv [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 résumer la variable v par les groupes dans le data.table:

 out <- DT[,list(SUM=sum(v)),by=list(x,y)] out xy 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 des colonnes plutôt que des lignes. Je peux accomplir ceci 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? Est-il possible de combiner ces opérations en une seule étape, en utilisant les opérations data.table?

Le package data.table implémente des fonctions de melt/dcast plus rapides (en C). Il a également des fonctionnalités supplémentaires en permettant de fondre et de lancer plusieurs colonnes . S’il vous plaît voir le nouveau remodelage efficace en utilisant data.tables sur Github.

Les fonctions fusion / dcast pour data.table sont disponibles depuis la version 1.0.0 et les fonctionnalités incluent:

  • Il n’est pas nécessaire de charger le paquetage reshape2 avant le moulage. 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. Pas plus dcast.data.table() . Il suffit d’utiliser 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 de variable et de value (en tant que facteurs ou non).

    • melt.data.table na.rm = TRUE est optimisé en interne pour supprimer les NA directement lors de la fusion et est donc beaucoup plus efficace.

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

  • dcast :

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

    • Utilisez la fonction rowid() directement dans formula pour générer une colonne d’ID, 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 reshape2 en reshape2 à reshape2 ?

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 gros morceau de code d’Arun ici sur SO . Donc, je suppose qu’il existe une solution data.table . 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 AB 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) AB 1 26499966 28166677 2 26499978 28166673 3 26500056 28166650 

En outre, 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 

METTRE À JOUR

Pour que cette solution fonctionne également pour des ensembles de données non équilibrés (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 sum, 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 

Les objects Data.table héritent de ‘data.frame’, vous pouvez donc utiliser tapply:

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

Vous pouvez utiliser dcast de la bibliothèque reshape2 . 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.