web-dev-qa-db-fra.com

Calculer la moyenne par groupe

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.

49
Jojo Ono

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.


Exemples

base fonctions

tapply:

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 

Paquets 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 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 baseR (comme split et la famille de fonctions apply), mais plyr 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

Des repères

10 rangées, 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 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:

  • Ce que vous connaissez déjà ou souhaitez connaître (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)
  • Quelle sortie il renvoie (numeric, data.frame ou data.table - ce dernier héritant de data.frame)

10 millions de lignes, 10 groupes

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)

benchmark 1e7 rows, 10 groups

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.

10 millions de lignes, 1000 groupes

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)

enter image description here

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.

101
Ari B. Friedman
aggregate(speed~dive,data=df,FUN=mean)
   dive     speed
1 dive1 0.7059729
2 dive2 0.5473777
6
James

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
5
Pierre Lafortune