web-dev-qa-db-fra.com

Comment additionner une variable par groupe

Disons que j'ai deux colonnes de données. Le premier contient des catégories telles que "Premier", "Deuxième", "Troisième", etc. Le second contient des chiffres qui représentent le nombre de fois où j'ai vu "Premier".

Par exemple:

Category     Frequency
First        10
First        15
First        5
Second       2
Third        14
Third        20
Second       3

Je veux trier les données par catégorie et additionner les fréquences:

Category     Frequency
First        30
Second       5
Third        34

Comment pourrais-je faire cela dans R?

307
user5243421

Utiliser aggregate:

aggregate(x$Frequency, by=list(Category=x$Category), FUN=sum)
  Category  x
1    First 30
2   Second  5
3    Third 34

Dans l'exemple ci-dessus, plusieurs dimensions peuvent être spécifiées dans list. Plusieurs métriques agrégées du même type de données peuvent être incorporées via cbind:

aggregate(cbind(x$Frequency, x$Metric2, x$Metric3) ...

(incorporant le commentaire @thelatemail), aggregate possède également une interface de formule

aggregate(Frequency ~ Category, x, sum)

Ou si vous souhaitez agréger plusieurs colonnes, vous pouvez utiliser la notation . (fonctionne également pour une colonne).

aggregate(. ~ Category, x, sum)

ou tapply:

tapply(x$Frequency, x$Category, FUN=sum)
 First Second  Third 
    30      5     34 

En utilisant ces données:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))
343
rcs

Plus récemment, vous pouvez également utiliser le package dplyr à cette fin:

library(dplyr)
x %>% 
  group_by(Category) %>% 
  summarise(Frequency = sum(Frequency))

#Source: local data frame [3 x 2]
#
#  Category Frequency
#1    First        30
#2   Second         5
#3    Third        34

Ou, pour plusieurs colonnes de résumé (fonctionne également avec une colonne):

x %>% 
  group_by(Category) %>% 
  summarise_each(funs(sum))

Mise à jour pour dplyr> = 0.5: summarise_each a été remplacé par summarise_all, summarise_at et summarise_if famille de fonctions dans dplyr.

Ou, si vous avez plusieurs colonnes à regrouper, , vous pouvez toutes les spécifier dans le group_by séparé par des virgules:

mtcars %>% 
  group_by(cyl, gear) %>%                            # multiple group columns
  summarise(max_hp = max(hp), mean_mpg = mean(mpg))  # multiple summary columns

Pour plus d'informations, y compris l'opérateur %>%, reportez-vous à la section introduction à dplyr .

186
docendo discimus

La réponse fournie par rcs fonctionne et est simple. Toutefois, si vous gérez des ensembles de données plus volumineux et que vous avez besoin d'une amélioration des performances, il existe une alternative plus rapide:

library(data.table)
data = data.table(Category=c("First","First","First","Second","Third", "Third", "Second"), 
                  Frequency=c(10,15,5,2,14,20,3))
data[, sum(Frequency), by = Category]
#    Category V1
# 1:    First 30
# 2:   Second  5
# 3:    Third 34
system.time(data[, sum(Frequency), by = Category] )
# user    system   elapsed 
# 0.008     0.001     0.009 

Comparons cela à la même chose en utilisant data.frame et ce qui précède:

data = data.frame(Category=c("First","First","First","Second","Third", "Third", "Second"),
                  Frequency=c(10,15,5,2,14,20,3))
system.time(aggregate(data$Frequency, by=list(Category=data$Category), FUN=sum))
# user    system   elapsed 
# 0.008     0.000     0.015 

Et si vous voulez conserver la colonne, voici la syntaxe:

data[,list(Frequency=sum(Frequency)),by=Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34

La différence deviendra plus perceptible avec des jeux de données plus volumineux, comme le montre le code ci-dessous:

data = data.table(Category=rep(c("First", "Second", "Third"), 100000),
                  Frequency=rnorm(100000))
system.time( data[,sum(Frequency),by=Category] )
# user    system   elapsed 
# 0.055     0.004     0.059 
data = data.frame(Category=rep(c("First", "Second", "Third"), 100000), 
                  Frequency=rnorm(100000))
system.time( aggregate(data$Frequency, by=list(Category=data$Category), FUN=sum) )
# user    system   elapsed 
# 0.287     0.010     0.296 

Pour plusieurs agrégations, vous pouvez combiner lapply et .SD comme suit

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
65
asieira

C'est un peu lié à cette question .

Vous pouvez aussi simplement utiliser le par() une fonction:

x2 <- by(x$Frequency, x$Category, sum)
do.call(rbind,as.list(x2))

Ces autres paquets (plyr, reshape) ont l'avantage de renvoyer un nom de données.fr, mais il est intéressant de se familiariser avec by () car c'est une fonction de base.

36
Shane
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
25
learnr

Plusieurs années plus tard, il suffit d'ajouter une autre solution de base R simple qui n'est pas présente ici pour une raison quelconque _ xtabs

xtabs(Frequency ~ Category, df)
# Category
# First Second  Third 
#    30      5     34 

Ou si vous voulez un data.frame back

as.data.frame(xtabs(Frequency ~ Category, df))
#   Category Freq
# 1    First   30
# 2   Second    5
# 3    Third   34
24
David Arenburg

Si x est une base de données contenant vos données, les opérations suivantes feront ce que vous voulez:

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
19
Rob Hyndman

Juste pour ajouter une troisième option:

require(doBy)
summaryBy(Frequency~Category, data=yourdataframe, FUN=sum)

EDIT: c'est une très vieille réponse. Maintenant, je recommanderais l’utilisation de group_by et summarise à partir de dplyr, comme dans la réponse @docendo.

16
dalloliogm

Bien que je sois récemment converti en dplyr pour la plupart de ces types d’opérations, le paquet sqldf est toujours vraiment agréable (et à mon humble avis, plus lisible).

Voici un exemple de réponse à cette question avec sqldf

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                  "Third", "Third", "Second")), 
                Frequency=c(10,15,5,2,14,20,3))

sqldf("select 
          Category
          ,sum(Frequency) as Frequency 
       from x 
       group by 
          Category")

##   Category Frequency
## 1    First        30
## 2   Second         5
## 3    Third        34
16
joemienko

Je trouve ave très utile (et efficace) lorsque vous devez appliquer différentes fonctions d'agrégation sur différentes colonnes (et que vous devez/souhaitez vous en tenir à la base R):

par exemple.

Compte tenu de cette entrée:

DF <-                
data.frame(Categ1=factor(c('A','A','B','B','A','B','A')),
           Categ2=factor(c('X','Y','X','X','X','Y','Y')),
           Samples=c(1,2,4,3,5,6,7),
           Freq=c(10,30,45,55,80,65,50))

> DF
  Categ1 Categ2 Samples Freq
1      A      X       1   10
2      A      Y       2   30
3      B      X       4   45
4      B      X       3   55
5      A      X       5   80
6      B      Y       6   65
7      A      Y       7   50

nous voulons grouper par Categ1 et Categ2 et calculer la somme de Samples et la moyenne de Freq.
Voici une solution possible en utilisant ave:

# create a copy of DF (only the grouping columns)
DF2 <- DF[,c('Categ1','Categ2')]

# add sum of Samples by Categ1,Categ2 to DF2 
# (ave repeats the sum of the group for each row in the same group)
DF2$GroupTotSamples <- ave(DF$Samples,DF2,FUN=sum)

# add mean of Freq by Categ1,Categ2 to DF2 
# (ave repeats the mean of the group for each row in the same group)
DF2$GroupAvgFreq <- ave(DF$Freq,DF2,FUN=mean)

# remove the duplicates (keep only one row for each group)
DF2 <- DF2[!duplicated(DF2),]

Résultat :

> DF2
  Categ1 Categ2 GroupTotSamples GroupAvgFreq
1      A      X               6           45
2      A      Y               9           40
3      B      X               7           50
6      B      Y               6           65
4
digEmAll

La dplyr::tally() récemment ajoutée rend cela plus facile que jamais:

tally(x, Category)

Category     n
First        30
Second       5
Third        34
4
dmca

Vous pouvez utiliser la fonction group.sum de package Rfast .

Category <- Rfast::as_integer(Category,result.sort=FALSE) # convert character to numeric. R's as.numeric produce NAs.
result <- Rfast::group.sum(Frequency,Category)
names(result) <- Rfast::Sort(unique(Category)
# 30 5 34

Rfast possède de nombreuses fonctions de groupe et group.sum en fait partie.

3
Csd

en utilisant cast au lieu de recast (note 'Frequency' est maintenant 'value')

df  <- data.frame(Category = c("First","First","First","Second","Third","Third","Second")
                  , value = c(10,15,5,2,14,20,3))

install.packages("reshape")

result<-cast(df, Category ~ . ,fun.aggregate=sum)

obtenir:

Category (all)
First     30
Second    5
Third     34
2
Grant Shannon