J'ai un grand bloc de données ressemblant à ceci:
df <- data.frame(dive=factor(sample(c("dive1","dive2"),10,replace=TRUE)),speed=runif(10))
> df
dive speed
1 dive1 0.80668490
2 dive1 0.53349584
3 dive2 0.07571784
4 dive2 0.39518628
5 dive1 0.84557955
6 dive1 0.69121443
7 dive1 0.38124950
8 dive2 0.22536126
9 dive1 0.04704750
10 dive2 0.93561651
Mon objectif est de faire la moyenne des valeurs d'une colonne lorsqu'une autre colonne est égale à une certaine valeur et de la répéter pour toutes les valeurs. c'est-à-dire que dans l'exemple ci-dessus, je voudrais renvoyer une moyenne pour la colonne speed
pour chaque valeur unique de la colonne dive
. Donc quand dive==dive1
, la moyenne de speed
est la suivante, et ainsi de suite pour chaque valeur de dive
.
Il y a plusieurs façons de le faire dans R. Plus précisément, by
, aggregate
, split
et plyr
, cast
, tapply
, data.table
, dplyr
, etc.
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 un aperçu plus approfondi de toute la catégorie de problèmes, et sa lecture en vaut la peine. Son package plyr
implémente la stratégie pour les structures de données générales et dplyr
est une performance d'implémentation plus récente adaptée aux trames de données. Ils permettent de résoudre des problèmes de même forme mais d'une complexité encore plus grande que celle-ci. Ils valent bien l’apprentissage en tant qu’outil général pour résoudre les problèmes de manipulation des données.
Les performances sont un problème pour les très grands ensembles de données et il est difficile de battre les solutions basées sur data.table
. Si vous ne traitez que des jeux de données de taille moyenne ou plus petits, prendre le temps d'apprendre data.table
Ne vaut probablement pas la peine. dplyr
peut aussi être rapide, c'est donc un bon choix si vous voulez accélérer les choses, mais vous n'avez pas vraiment besoin de l'évolutivité de data.table
.
Beaucoup des autres solutions ci-dessous ne nécessitent aucun package supplémentaire. Certaines d'entre elles sont même assez rapides sur des jeux de données de moyenne à grande taille. Leur principal inconvénient est soit la métaphore, soit la flexibilité. Par métaphore, je veux dire qu’il s’agit d’un outil conçu pour permettre à quelque chose d’autre de forcer quelqu'un d'autre à résoudre ce type particulier de problème de manière "intelligente". Par flexibilité, je veux dire qu’ils n’ont pas la capacité de résoudre un aussi large éventail de problèmes similaires ou de produire facilement une sortie ordonnée.
base
fonctionstapply
:
tapply(df$speed, df$dive, mean)
# dive1 dive2
# 0.5419921 0.5103974
aggregate
:
aggregate
prend dans data.frames, génère des data.frames 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 en compte les vecteurs et leur applique une fonction. Cependant, sa sortie 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 cela, pour des utilisations simples de by
, la méthode as.data.frame
De la bibliothèque taRifx
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'effectue que la partie "scission" de la stratégie scission-application-combinaison. Pour que le reste fonctionne, j'écrirai une petite fonction qui utilise sapply
pour apply-combine. sapply
simplifie automatiquement le résultat autant que possible. Dans notre cas, cela signifie un vecteur plutôt qu'un nom de données.fr, car 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
data.table :
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 la page officielle dit à propos de plyr
:
Il est déjà possible de le faire avec les fonctions
base
R (commesplit
et la famille de fonctionsapply
), maisplyr
rend les choses un peu plus faciles avec :
- noms, arguments et sorties totalement cohérents
- parallélisation pratique via le package
foreach
- entrée et sortie de data.frames, matrices et listes
- barres de progression pour suivre les opérations de longue durée
- récupération d'erreur intégrée et messages d'erreur informatifs
- étiquettes qui sont maintenues dans toutes les transformations
En d'autres termes, si vous apprenez un outil pour la manipulation split-apply-combine, ce devrait être 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
reshape2 :
La bibliothèque reshape2
N'est pas conçue avec split-apply-combine comme cible principale. Au lieu de cela, il utilise une stratégie de fusion/diffusion en deux parties pour effectuer une grande variété de tâches de remodelage des données . Cependant, puisqu'il permet une fonction d'agrégation, il peut être utilisé pour résoudre ce problème. Ce ne serait pas mon premier choix pour les opérations de combinaison d’application divisée, mais ses capacités de remodelage sont puissantes et vous devriez donc apprendre ce package également.
library(reshape2)
dcast( melt(df), variable ~ dive, mean)
# Using dive as id variables
# variable dive1 dive2
# 1 speed 0.5790946 0.4864489
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 temps système et se situe donc dans la moyenne pour les petits ensembles de données. Ce sont des microsecondes, alors les différences sont triviales. Toutes les approches fonctionnent bien ici, et vous devriez choisir en fonction de:
plyr
vaut toujours la peine d’être appris pour sa souplesse; data.table
Vaut la peine d’être appris si vous envisagez d’analyser d’énormes jeux de données; by
et aggregate
et split
sont tous des fonctions de base R et sont donc universellement disponibles)Mais que faire si nous avons un grand jeu 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)
Ensuite, data.table
Ou dplyr
en utilisant l'opération sur data.table
S est clairement le chemin à parcourir. Certaines approches (aggregate
et dcast
) commencent à paraître très lentes.
Si vous avez plus de groupes, la différence devient plus prononcée. Avec 1 000 groupes et les mêmes 10 ^ 7 lignes:
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)
Donc data.table
Continue à bien évoluer, et dplyr
opérer sur un data.table
Fonctionne également bien, avec dplyr
sur data.frame
Proche d'un ordre de magnitude plus lente. La stratégie split
/sapply
semble mal évoluer en ce qui concerne le nombre de groupes (ce qui signifie que split()
est probablement lent et que sapply
est rapide). by
continue d'être relativement efficace: au bout de 5 secondes, l'utilisateur le remarque nettement, mais pour un ensemble de données aussi volumineux, ce n'est pas déraisonnable. Néanmoins, si vous travaillez régulièrement avec des jeux de données de cette taille, data.table
Est clairement le chemin à parcourir - 100% data.table pour une meilleure performance ou dplyr
avec dplyr
en utilisant data.table
comme alternative viable.
aggregate(speed~dive,data=df,FUN=mean)
dive speed
1 dive1 0.7059729
2 dive2 0.5473777
mise à jour 2015 avec dplyr:
df %>% group_by(dive) %>% summarise(percentage = mean(speed))
Source: local data frame [2 x 2]
dive percentage
1 dive1 0.4777462
2 dive2 0.6726483