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