93 votes

Calculer la moyenne par groupe

J'ai un grand cadre de données qui ressemble à 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 d'obtenir 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. Par exemple, dans l'exemple ci-dessus, je voudrais obtenir une moyenne pour la colonne speed pour chaque valeur unique de la colonne dive . Alors quand dive==dive1 la moyenne pour speed est ceci et ainsi de suite pour chaque valeur de dive .

169voto

Ari B. Friedman Points 24940

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)

benchmark 10 rows

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)

benchmark 1e7 rows, 10 groups

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)

enter image description here

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.

10voto

Pierre Lafortune Points 5107

Mise à jour de 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

9voto

James Points 24725
aggregate(speed~dive,data=df,FUN=mean)
   dive     speed
1 dive1 0.7059729
2 dive2 0.5473777

3voto

Ajout de l'approche alternative de la base R, qui reste rapide dans divers cas.

rowsummean <- function(df) {
  rowsum(df$speed, df$dive) / tabulate(df$dive)
}

J'emprunte les repères à @Ari :

10 rangs, 2 groupes

res1

10 millions de lignes, 10 groupes

res2

10 millions de lignes, 1000 groupes

res3

1voto

RCchelsie Points 27

Avec la nouvelle fonction across :

df %>% 
  group_by(dive) %>% 
  summarise(across(speed, mean, na.rm = TRUE))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X