Calculer la moyenne par groupe

J’ai un grand bloc de données ressemblant à ceci:

df  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 objective est de faire la moyenne des valeurs d’une colonne lorsqu’une autre colonne est égale à une certaine valeur et de répéter cette opération pour toutes les valeurs. C’est-à-dire que dans l’exemple ci-dessus, je voudrais retourner une moyenne pour la speed de la colonne pour chaque valeur unique de la dive colonne. Donc, lors de la dive==dive1 , la speed moyenne est la suivante et ainsi de suite pour chaque valeur de dive .

Il y a plusieurs façons de faire cela dans R. Plus précisément, by , aggregate , split et plyr , cast , tapply , data.table , dplyr , etc.

En gros, ces problèmes sont de la forme split-apply-combine. Hadley Wickham a écrit un bel article qui vous permettra de mieux comprendre toute la catégorie des problèmes, et cela vaut la peine de le lire. Son package plyr implémente la stratégie pour les structures de données générales, et dplyr est une nouvelle performance d’implémentation adaptée aux dplyr 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 la peine d’être appris en tant qu’outil général pour résoudre les problèmes de manipulation de données.

Les performances sont un problème pour les jeux de données très volumineux, et il est difficile de data.table 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 n’en vaut probablement pas la peine. dplyr peut également être rapide, c’est donc un bon choix si vous voulez accélérer les choses, mais vous n’avez pas besoin de l’évolutivité de data.table .

La plupart des autres solutions ci-dessous ne nécessitent aucun paquet supplémentaire. Certains d’entre eux sont même assez rapides sur les ensembles de données de taille moyenne. Leur principal inconvénient est soit la métaphore, soit la flexibilité. Par métaphore, je veux dire que c’est un outil conçu pour que quelque chose d’autre soit contraint à résoudre ce type de problème de manière «intelligente». Par flexibilité, je veux dire qu’ils n’ont pas la capacité de résoudre un éventail de problèmes similaires ou de produire facilement des résultats nets.


Exemples

fonctions de base

tapply :

 tapply(df$speed, df$dive, mean) # dive1 dive2 # 0.5419921 0.5103974 

aggregate :

aggregate prend 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 des vecteurs et leur applique une fonction. Cependant, sa sortie n’est pas 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 as.data.frame , pour les utilisations simples de la méthode as.data.frame dans 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’exécute que la partie "split" de la stratégie split-apply-combine. Pour faire le rest, je vais écrire 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 data.frame, puisque nous n'avons qu'une 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 

Packs externes

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 dplyr de dplyr )

Voici ce que la page officielle dit à propos de plyr :

Il est déjà possible de le faire avec les fonctions de base R (comme la famille split et la famille de fonctions plyr ), mais plyr facilite les choses avec:

  • noms, arguments et sorties totalement cohérents
  • Parallélisation pratique via le package foreach
  • entrée et sortie de data.frames, masortingces 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 conservées dans toutes les transformations

En d'autres termes, si vous apprenez un outil pour la manipulation split-apply-combine, il 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 

remodeler2 :

La bibliothèque reshape2 n'est pas conçue avec focus principal sur split-apply-combine. Au lieu de cela, il utilise une stratégie de fusion / fusion en deux parties pour effectuer 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 split-apply-combine, mais ses capacités de remodelage sont puissantes et vous devriez donc également apprendre ce package.

 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 lignes, 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) 

benchmark 10 lignes

Comme d’habitude, data.table a un peu plus de temps à data.table à la moyenne pour les petits ensembles de données. Ce sont des microsecondes, cependant, les différences sont sortingviales. Toutes les approches fonctionnent bien ici, et vous devez choisir en fonction de:

  • Ce que vous connaissez déjà ou que vous souhaitez connaître ( plyr mérite toujours d’être plyr pour sa flexibilité; data.table vaut la peine d’être data.table si vous envisagez d’parsingr des ensembles de données data.table ; toutes les fonctions de base R et donc universellement disponible)
  • Quel résultat il retourne (numeric, data.frame ou data.table - le dernier hérite de data.frame)

10 millions de lignes, 10 groupes

Mais que faire si nous avons un grand dataset? 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) 

benchmark 1e7 lignes, 10 groupes

Alors, data.table ou dplyr utilisant le fonctionnement sur data.table s est clairement la voie à suivre. Certaines approches ( aggregate et dcast ) commencent à paraître très lentes.

10 millions de lignes, 1000 groupes

Si vous avez plus de groupes, la différence devient plus prononcée. Avec 1000 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) 

entrer la description de l'image ici

Donc, data.table continue à bien dplyr , et dplyr fonctionnant sur un data.table fonctionne également bien, avec dplyr sur data.frame près d'un ordre de grandeur plus lent. La stratégie split / sapply semble mal sapply dans le nombre de groupes (ce qui signifie que le split() est probablement lent et que le sapply est rapide). by continue à être relativement efficace - à 5 secondes, il est définitivement perceptible pour l'utilisateur mais pour un dataset, ce n'est toujours pas déraisonnable. Cependant, si vous travaillez régulièrement avec des ensembles de données de cette taille, data.table est clairement la solution: 100% data.table pour une performance dplyr ou dplyr avec dplyr 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