Il existe de nombreuses façons de le faire dans R. Plus précisément, by
, aggregate
, split
et plyr
, cast
, tapply
, data.table
, dplyr
et ainsi de suite.
De manière générale, ces problèmes sont de la forme "split-apply-combine". Hadley Wickham a écrit un bel article qui vous donnera une vision plus approfondie de toute cette catégorie de problèmes, et il vaut la peine d'être lu. Son site plyr
met en œuvre la stratégie pour les structures de données générales, et dplyr
est une implémentation plus récente dont les performances sont adaptées aux cadres de données. Elles permettent de résoudre des problèmes de la même forme mais d'une complexité encore plus grande que celle-ci. Ils valent la peine d'être appris en tant qu'outil général pour résoudre les problèmes de manipulation de données.
La performance est un problème sur les très grands ensembles de données, et pour cela il est difficile de battre les solutions basées sur data.table
. Cependant, si vous ne traitez que des ensembles de données de taille moyenne ou plus petite, prendre le temps d'apprendre data.table
n'en vaut probablement pas la peine. dplyr
peut également être rapide, ce qui en fait un bon choix si vous voulez accélérer les choses, mais que vous n'avez pas besoin de l'évolutivité de l'option data.table
.
La plupart des autres solutions ci-dessous ne nécessitent pas de paquetage supplémentaire. Certaines d'entre elles sont même assez rapides sur des ensembles de données de taille moyenne. Leur principal inconvénient est soit la métaphore, soit la flexibilité. Par métaphore, j'entends qu'il s'agit d'un outil conçu pour autre chose qui est contraint de résoudre ce type particulier de problème d'une manière "intelligente". Par flexibilité, j'entends qu'ils n'ont pas la capacité de résoudre une gamme aussi large de problèmes similaires ou de produire facilement des résultats bien ordonnés.
Exemples
base
fonctions
tapply
:
tapply(df$speed, df$dive, mean)
# dive1 dive2
# 0.5419921 0.5103974
aggregate
:
aggregate
prend des data.frames en entrée, produit des data.frames en sortie, et utilise une interface de formule.
aggregate( speed ~ dive, df, mean )
# dive speed
# 1 dive1 0.5790946
# 2 dive2 0.4864489
by
:
Dans sa forme la plus conviviale, il prend des vecteurs et leur applique une fonction. Cependant, son résultat n'est pas sous une forme très manipulable.. :
res.by <- by(df$speed, df$dive, mean)
res.by
# df$dive: dive1
# [1] 0.5790946
# ---------------------------------------
# df$dive: dive2
# [1] 0.4864489
Pour contourner ce problème, pour les utilisations simples de by
el as.data.frame
dans la méthode taRifx
la bibliothèque fonctionne :
library(taRifx)
as.data.frame(res.by)
# IDX1 value
# 1 dive1 0.6736807
# 2 dive2 0.4051447
split
:
Comme son nom l'indique, il n'exécute que la partie "split" de la stratégie split-apply-combine. Pour faire fonctionner le reste, je vais écrire une petite fonction qui utilise sapply
pour appliquer-combiner. sapply
simplifie automatiquement le résultat autant que possible. Dans notre cas, cela signifie un vecteur plutôt qu'un data.frame, puisque nous n'avons qu'une seule dimension de résultats.
splitmean <- function(df) {
s <- split( df, df$dive)
sapply( s, function(x) mean(x$speed) )
}
splitmean(df)
# dive1 dive2
# 0.5790946 0.4864489
Paquets externes
table.de.données :
library(data.table)
setDT(df)[ , .(mean_speed = mean(speed)), by = dive]
# dive mean_speed
# 1: dive1 0.5419921
# 2: dive2 0.5103974
dplyr
:
library(dplyr)
group_by(df, dive) %>% summarize(m = mean(speed))
plyr
(le précurseur de dplyr
)
Voici ce que le page officielle a à dire sur plyr
:
Il est déjà possible de le faire avec base
Les fonctions R (comme split
et le site apply
famille de fonctions), mais plyr
rend tout cela un peu plus facile avec :
- des noms, des arguments et des résultats totalement cohérents
- parallélisme pratique grâce à la
foreach
paquet
- entrée et sortie de cadres de données, de matrices et de listes
- des barres de progression pour suivre les opérations de longue durée
- récupération intégrée des erreurs et messages d'erreur informatifs
- des étiquettes qui sont maintenues à travers toutes les transformations
En d'autres termes, si vous n'apprenez qu'un seul outil pour la manipulation de type "split-apply-combine", c'est le suivant plyr
.
library(plyr)
res.plyr <- ddply( df, .(dive), function(x) mean(x$speed) )
res.plyr
# dive V1
# 1 dive1 0.5790946
# 2 dive2 0.4864489
remodeler2 :
El reshape2
n'a pas été conçue dans une optique de fractionnement, d'application et de combinaison. Au lieu de cela, elle utilise une stratégie de fusion/combinaison en deux parties afin de performer m une grande variété de tâches de remodelage des données . Cependant, comme il permet une fonction d'agrégation, il peut être utilisé pour ce problème. Ce ne serait pas mon premier choix pour les opérations de fractionnement-application-combinaison, mais ses capacités de remodelage sont puissantes et vous devriez donc apprendre ce paquet également.
library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
# variable dive1 dive2
# 1 speed 0.5790946 0.4864489
Repères
10 rangs, 2 groupes
library(microbenchmark)
m1 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[, mean(speed), by = dive],
summarize( group_by(df, dive), m = mean(speed) ),
summarize( group_by(dt, dive), m = mean(speed) )
)
> print(m1, signif = 3)
Unit: microseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 302 325 343.9 342 362 396 100 b
aggregate(speed ~ dive, df, mean) 904 966 1012.1 1020 1060 1130 100 e
splitmean(df) 191 206 249.9 220 232 1670 100 a
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1310 1358.1 1340 1380 2740 100 f
dcast(melt(df), variable ~ dive, mean) 2150 2330 2440.7 2430 2490 4010 100 h
dt[, mean(speed), by = dive] 599 629 667.1 659 704 771 100 c
summarize(group_by(df, dive), m = mean(speed)) 663 710 774.6 744 782 2140 100 d
summarize(group_by(dt, dive), m = mean(speed)) 1860 1960 2051.0 2020 2090 3430 100 g
autoplot(m1)
Comme d'habitude, data.table
a un peu plus de frais généraux et se situe dans la moyenne pour les petits ensembles de données. Mais comme il s'agit de microsecondes, les différences sont insignifiantes. N'importe laquelle des approches fonctionne bien ici, et vous devriez choisir en fonction de :
- Ce avec quoi vous êtes déjà familiarisé ou ce avec quoi vous voulez être familiarisé (
plyr
vaut toujours la peine d'être appris pour sa flexibilité ; data.table
vaut la peine d'être appris si vous envisagez d'analyser d'énormes ensembles de données ; by
y aggregate
y split
sont toutes des fonctions de base de R et sont donc universellement disponibles)
- La sortie qu'il renvoie (numérique, data.frame, ou data.table -- ce dernier hérite de data.frame)
10 millions de lignes, 10 groupes
Mais que faire si nous avons un grand ensemble de données ? Essayons 10^7 lignes réparties sur dix groupes.
df <- data.frame(dive=factor(sample(letters[1:10],10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
m2 <- microbenchmark(
by( df$speed, df$dive, mean),
aggregate( speed ~ dive, df, mean ),
splitmean(df),
ddply( df, .(dive), function(x) mean(x$speed) ),
dcast( melt(df), variable ~ dive, mean),
dt[,mean(speed),by=dive],
times=2
)
> print(m2, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 720 770 799.1 791 816 958 100 d
aggregate(speed ~ dive, df, mean) 10900 11000 11027.0 11000 11100 11300 100 h
splitmean(df) 974 1040 1074.1 1060 1100 1280 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1050 1080 1110.4 1100 1130 1260 100 f
dcast(melt(df), variable ~ dive, mean) 2360 2450 2492.8 2490 2520 2620 100 g
dt[, mean(speed), by = dive] 119 120 126.2 120 122 212 100 a
summarize(group_by(df, dive), m = mean(speed)) 517 521 531.0 522 532 620 100 c
summarize(group_by(dt, dive), m = mean(speed)) 154 155 174.0 156 189 321 100 b
autoplot(m2)
Puis data.table
o dplyr
en utilisant l'exploitation sur data.table
est clairement la voie à suivre. Certaines approches ( aggregate
y dcast
) commencent à être très lents.
10 millions de lignes, 1 000 groupes
Si vous avez plus de groupes, la différence devient plus prononcée. Avec 1 000 groupes et les mêmes 10^7 rangées :
df <- data.frame(dive=factor(sample(seq(1000),10^7,replace=TRUE)),speed=runif(10^7))
dt <- data.table(df)
setkey(dt,dive)
# then run the same microbenchmark as above
print(m3, signif = 3)
Unit: milliseconds
expr min lq mean median uq max neval cld
by(df$speed, df$dive, mean) 776 791 816.2 810 828 925 100 b
aggregate(speed ~ dive, df, mean) 11200 11400 11460.2 11400 11500 12000 100 f
splitmean(df) 5940 6450 7562.4 7470 8370 11200 100 e
ddply(df, .(dive), function(x) mean(x$speed)) 1220 1250 1279.1 1280 1300 1440 100 c
dcast(melt(df), variable ~ dive, mean) 2110 2190 2267.8 2250 2290 2750 100 d
dt[, mean(speed), by = dive] 110 111 113.5 111 113 143 100 a
summarize(group_by(df, dive), m = mean(speed)) 625 630 637.1 633 644 701 100 b
summarize(group_by(dt, dive), m = mean(speed)) 129 130 137.3 131 142 213 100 a
autoplot(m3)
Alors data.table
continue à bien se développer, et dplyr
fonctionnant sur un data.table
fonctionne également bien, avec dplyr
sur data.frame
près d'un ordre de grandeur plus lent. Le site split
/ sapply
La stratégie semble peu évolutive en ce qui concerne le nombre de groupes (ce qui signifie que le nombre de groupes est plus élevé que le nombre de groupes). split()
est probablement lent et le sapply
est rapide). by
continue d'être relativement efficace - à 5 secondes, c'est certainement perceptible pour l'utilisateur, mais pour un ensemble de données de cette taille, ce n'est pas déraisonnable. Néanmoins, si vous travaillez régulièrement avec des ensembles de données de cette taille, data.table
est clairement la voie à suivre - 100% data.table pour les meilleures performances ou dplyr
con dplyr
en utilisant data.table
comme une alternative viable.