Comment additionner une variable par groupe

357

J'ai un bloc de données avec deux colonnes. La première colonne contient des catégories telles que "Première", "Deuxième", "Troisième", et la deuxième colonne a des nombres qui représentent le nombre de fois où j'ai vu les groupes spécifiques de "Catégorie".

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 toutes les fréquences:

Category     Frequency
First        30
Second       5
Third        34

Comment pourrais-je faire cela dans R?

user5243421
la source
1
Le moyen le plus rapide en base R est rowsum.
Michael M

Réponses:

387

En utilisant 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 le list. Plusieurs métriques agrégées du même type de données peuvent être intégrées via cbind:

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

(intégrant le commentaire @thelatemail), aggregatepossè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))
rcs
la source
4
@AndrewMcKinlay, R utilise le tilde pour définir des formules symboliques, pour les statistiques et autres fonctions. Il peut être interprété comme "fréquence du modèle par catégorie" ou "fréquence selon la catégorie" . Toutes les langues n'utilisent pas d'opérateur spécial pour définir une fonction symbolique, comme cela est fait dans R ici. Peut-être qu'avec cette «interprétation en langage naturel» de l'opérateur tilde, cela devient plus significatif (et même intuitif). Personnellement, je trouve cette représentation de formule symbolique meilleure que certaines des alternatives les plus verbeuses.
r2evans
1
Étant nouveau dans R (et posant les mêmes types de questions que l'OP), je bénéficierais d'un peu plus de détails sur la syntaxe derrière chaque alternative. Par exemple, si j'ai une table source plus grande et que je souhaite ne sous-sélectionner que deux dimensions et des métriques additionnées, puis-je adapter l'une de ces méthodes? Dur à dire.
Dodecaphone
236

Vous pouvez également utiliser le package dplyr à cet effet:

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 récapitulatives (fonctionne également avec une colonne):

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

Voici quelques exemples supplémentaires de récapitulation des données par groupe à l'aide des fonctions dplyr à l'aide du jeu de données intégré mtcars:

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

# summarise all columns except grouping columns using "sum" 
mtcars %>% 
  group_by(cyl) %>% 
  summarise_all(sum)

# summarise all columns except grouping columns using "sum" and "mean"
mtcars %>% 
  group_by(cyl) %>% 
  summarise_all(funs(sum, mean))

# multiple grouping columns
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise_all(funs(sum, mean))

# summarise specific variables, not all
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise_at(vars(qsec, mpg, wt), funs(sum, mean))

# summarise specific variables (numeric columns except grouping columns)
mtcars %>% 
  group_by(gear) %>% 
  summarise_if(is.numeric, funs(mean))

Pour plus d'informations, y compris l' %>%opérateur, consultez l' introduction à dplyr .

talat
la source
1
Quelle est sa vitesse par rapport à la table de données et aux alternatives agrégées présentées dans d'autres réponses?
asieira
5
@asieira, qui est le plus rapide et l'ampleur de la différence (ou si la différence est notable) dépendra toujours de la taille de vos données. En règle générale, pour les grands ensembles de données, par exemple certains Go, data.table sera très probablement le plus rapide. Sur des données plus petites, data.table et dplyr sont souvent proches, également en fonction du nombre de groupes. Cependant, les données, la table et dplyr seront beaucoup plus rapides que les fonctions de base (peuvent être 100 à 1000 fois plus rapides pour certaines opérations). Voir aussi ici
talat
1
À quoi les «amusements» font-ils référence dans le deuxième exemple?
lauren.marietta
@ lauren.marietta, vous pouvez spécifier la ou les fonctions que vous souhaitez appliquer comme résumé dans l' funs()argument summarise_allet ses fonctions associées ( summarise_at, summarise_if)
talat
76

La réponse fournie par rcs fonctionne et est simple. Cependant, si vous manipulez des ensembles de données plus volumineux et 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 souhaitez 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 visible avec des ensembles de données plus importants, 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 lapplyet .SDcomme suit

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
asieira
la source
13
+1 Mais 0,296 contre 0,059 n'est pas particulièrement impressionnant. La taille des données doit être beaucoup plus grande que 300 000 lignes, et avec plus de 3 groupes, pour que data.table brille. Nous allons essayer de prendre en charge plus de 2 milliards de lignes bientôt par exemple, car certains utilisateurs de data.table ont 250 Go de RAM et GNU R prend désormais en charge une longueur> 2 ^ 31.
Matt Dowle
2
Vrai. Il s'avère que je n'ai pas toute cette RAM, et j'essayais simplement de fournir des preuves des performances supérieures de data.table. Je suis sûr que la différence serait encore plus grande avec plus de données.
asieira
1
J'ai eu 7 mil observations dplyr a pris 0,3 secondes et l'agrégat () a pris 22 secondes pour terminer l'opération. J'allais le poster sur ce sujet et tu m'as battu dessus!
zazu
3
Il existe un moyen encore plus court d'écrire ceci data[, sum(Frequency), by = Category]. Vous pouvez utiliser .Nce qui remplace la sum()fonction. data[, .N, by = Category]. Voici une astuce utile: s3.amazonaws.com/assets.datacamp.com/img/blog/…
Stophface
3
L'utilisation de .N serait équivalente à sum (Frequency) uniquement si toutes les valeurs de la colonne Frequency étaient égales à 1, car .N compte le nombre de lignes dans chaque ensemble agrégé (.SD). Et ce n'est pas le cas ici.
asieira
41

Vous pouvez également utiliser la fonction by () :

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

Ces autres packages (plyr, remodelage) ont l'avantage de renvoyer un data.frame, mais cela vaut la peine d'être familier avec by () car c'est une fonction de base.

Shane
la source
28

Plusieurs années plus tard, juste pour 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.frameretour

as.data.frame(xtabs(Frequency ~ Category, df))
#   Category Freq
# 1    First   30
# 2   Second    5
# 3    Third   34
David Arenburg
la source
27
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
learnr
la source
23

Si xest une trame de données avec vos données, alors ce qui suit fera ce que vous voulez:

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

Bien que je sois récemment devenu un converti dplyrpour la plupart de ces types d'opérations, le sqldfpaquet est toujours très agréable (et à mon humble avis plus lisible) pour certaines choses.

Voici un exemple de réponse à cette question 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
joemienko
la source
18

Juste pour ajouter une troisième option:

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

EDIT: c'est une réponse très ancienne. Maintenant, je recommanderais l'utilisation de group_byet summarisedepuis dplyr, comme dans la réponse @docendo.

dalloliogm
la source
7

Je trouve avetrès utile (et efficace) lorsque vous devez appliquer différentes fonctions d'agrégation sur différentes colonnes (et vous devez / voulez 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 regrouper par Categ1et Categ2et calculer la somme de Sampleset 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
digEmAll
la source
6

L'ajout récemment dplyr::tally()fait maintenant plus facile que jamais:

tally(x, Category)

Category     n
First        30
Second       5
Third        34
dmca
la source
6

Vous pouvez utiliser la fonction group.sumdu 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 a de nombreuses fonctions de groupe et engroup.sumfait partie.

Manos Papadakis
la source
4

utiliser à la castplace de recast(la 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
Grant Shannon
la source
2

Une autre solution qui renvoie des sommes par groupes dans une matrice ou une trame de données et est courte et rapide:

rowsum(x$Frequency, x$Category)
Karolis Koncevičius
la source
Joliment, et en effet rapide.
jay.sf
0

Depuis dplyr 1.0.0, la across()fonction pourrait être utilisée:

df %>%
 group_by(Category) %>%
 summarise(across(Frequency, sum))

  Category Frequency
  <chr>        <int>
1 First           30
2 Second           5
3 Third           34

Si vous êtes intéressé par plusieurs variables:

df %>%
 group_by(Category) %>%
 summarise(across(c(Frequency, Frequency2), sum))

  Category Frequency Frequency2
  <chr>        <int>      <int>
1 First           30         55
2 Second           5         29
3 Third           34        190

Et la sélection des variables à l'aide des assistants de sélection:

df %>%
 group_by(Category) %>%
 summarise(across(starts_with("Freq"), sum))

  Category Frequency Frequency2 Frequency3
  <chr>        <int>      <int>      <dbl>
1 First           30         55        110
2 Second           5         29         58
3 Third           34        190        380

Exemples de données:

df <- read.table(text = "Category Frequency Frequency2 Frequency3
                 1    First        10         10         20
                 2    First        15         30         60
                 3    First         5         15         30
                 4   Second         2          8         16
                 5    Third        14         70        140
                 6    Third        20        120        240
                 7   Second         3         21         42",
                 header = TRUE,
                 stringsAsFactors = FALSE)
tmfmnk
la source