Come sommare una variabile per gruppo


357

Ho un frame di dati con due colonne. La prima colonna contiene categorie come "Prima", "Seconda", "Terza", e la seconda colonna ha numeri che rappresentano il numero di volte in cui ho visto i gruppi specifici da "Categoria".

Per esempio:

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

Voglio ordinare i dati per categoria e sommare tutte le frequenze:

Category     Frequency
First        30
Second       5
Third        34

Come lo farei in R?


1
Il modo più veloce nella base R è rowsum.
Michael M,

Risposte:


387

Utilizzando aggregate:

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

Nell'esempio sopra, è possibile specificare più dimensioni in list. È possibile incorporare più metriche aggregate dello stesso tipo di dati tramite cbind:

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

(incorporando il commento di @thelatemail), aggregateha anche un'interfaccia formula

aggregate(Frequency ~ Category, x, sum)

Oppure, se vuoi aggregare più colonne, puoi usare la .notazione (funziona anche per una colonna)

aggregate(. ~ Category, x, sum)

oppure tapply:

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

Utilizzando questi dati:

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

4
@AndrewMcKinlay, R usa la tilde per definire formule simboliche, per statistiche e altre funzioni. Può essere interpretato come "Frequenza modello per categoria" o "Frequenza dipendente dalla categoria" . Non tutte le lingue usano un operatore speciale per definire una funzione simbolica, come fatto qui in R. Forse con quella "interpretazione del linguaggio naturale" dell'operatore tilde, diventa più significativo (e persino intuitivo). Personalmente trovo questa rappresentazione della formula simbolica migliore di alcune delle alternative più dettagliate.
r2evans,

1
Essendo nuovo di R (e ponendo gli stessi tipi di domande del PO), beneficerei di alcuni dettagli in più sulla sintassi dietro ogni alternativa. Ad esempio, se ho una tabella di origine più grande e desidero selezionare solo due dimensioni più le metriche sommate, posso adattare uno di questi metodi? Difficile da dire.
Dodecaphone,

236

È inoltre possibile utilizzare il pacchetto dplyr a tale scopo:

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

Oppure, per più colonne di riepilogo (funziona anche con una colonna):

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

Ecco alcuni altri esempi di come riepilogare i dati per gruppo usando le funzioni dplyr usando il set di dati integrato 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))

Per ulteriori informazioni, incluso l' %>%operatore, vedere l' introduzione a dplyr .


1
Quanto è veloce rispetto alle alternative data.table e aggregate presentate in altre risposte?
asieira,

5
@asieira, che è più veloce e quanto è grande la differenza (o se la differenza è evidente) dipenderà sempre dalla dimensione dei tuoi dati. In genere, per set di dati di grandi dimensioni, ad esempio alcuni GB, data.table molto probabilmente sarà il più veloce. Con dimensioni dei dati inferiori, data.table e dplyr sono spesso vicini, anche in base al numero di gruppi. Sia dati, tabella che dplyr saranno molto più veloci delle funzioni di base, tuttavia (per alcune operazioni possono essere 100-1000 volte più veloci). Vedi anche qui
talat

1
A cosa si riferiscono i "divertimenti" nel secondo esempio?
lauren.marietta,

@ lauren.marietta è possibile specificare le funzioni che si desidera applicare come riepilogo all'interno funs()dell'argomento summarise_alle delle relative funzioni ( summarise_at, summarise_if)
talat

76

La risposta fornita da rcs funziona ed è semplice. Tuttavia, se stai gestendo set di dati più grandi e hai bisogno di un aumento delle prestazioni, c'è un'alternativa più veloce:

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 

Confrontiamolo con la stessa cosa usando data.frame e sopra sopra:

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 

E se vuoi mantenere la colonna questa è la sintassi:

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

La differenza diventerà più evidente con set di dati più grandi, come dimostra il codice seguente:

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 

Per più aggregazioni, è possibile combinare lapplye .SDcome segue

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

13
+1 Ma 0,296 vs 0,059 non è particolarmente impressionante. La dimensione dei dati deve essere molto più grande di 300k righe e con più di 3 gruppi, affinché data.table possa brillare. Cercheremo di supportare presto più di 2 miliardi di righe, ad esempio, poiché alcuni utenti di data.table hanno 250 GB di RAM e GNU R ora supporta lunghezza> 2 ^ 31.
Matt Dowle,

2
Vero. Si scopre che non ho tutta quella RAM, e stavo semplicemente cercando di fornire alcune prove delle prestazioni superiori di data.table. Sono sicuro che la differenza sarebbe ancora più grande con più dati.
asieira,

1
Ho ricevuto 7 milioni di osservazioni dplyr impiegando .3 secondi e aggregate () ha impiegato 22 secondi per completare l'operazione. Stavo per pubblicarlo su questo argomento e mi hai battuto!
zazu,

3
C'è un modo ancora più breve per scrivere questo data[, sum(Frequency), by = Category]. È possibile utilizzare .Nquale sostituisce la sum()funzione. data[, .N, by = Category]. Ecco un utile cheatsheet
Stophface

3
L'uso di .N equivarrebbe alla somma (Frequenza) solo se tutti i valori nella colonna Frequenza fossero uguali a 1, perché .N conta il numero di righe in ciascun set aggregato (.SD). E questo non è il caso qui.
asieira,

41

Puoi anche usare la funzione by () :

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

Questi altri pacchetti (plyr, reshape) hanno il vantaggio di restituire un data.frame, ma vale la pena familiarizzare con () poiché è una funzione di base.


28

Diversi anni dopo, solo per aggiungere un'altra semplice soluzione di base R che non è presente qui per qualche motivo- xtabs

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

O se vuoi un data.frameritorno

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

27
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))

23

Se xè un frame di dati con i tuoi dati, quindi quanto segue farà ciò che desideri:

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

19

Mentre di recente sono diventato un convertito dplyrper la maggior parte di questi tipi di operazioni, il sqldfpacchetto è ancora molto bello (e IMHO più leggibile) per alcune cose.

Ecco un esempio di come è possibile rispondere a questa domanda 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

18

Solo per aggiungere una terza opzione:

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

EDIT: questa è una risposta molto vecchia. Ora consiglierei l'uso di group_bye summariseda dplyr, come nella risposta @docendo.


7

Trovo avemolto utile (ed efficiente) quando è necessario applicare diverse funzioni di aggregazione su colonne diverse (e si deve / si desidera attenersi alla base R):

per esempio

Dato questo input:

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

vogliamo gruppo da Categ1e Categ2e calcolare la somma di Samplese intendiamo di Freq.
Ecco una possibile soluzione usando 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),]

Risultato:

> 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

6

Il recente aggiunto dplyr::tally()ora rende questo più facile che mai:

tally(x, Category)

Category     n
First        30
Second       5
Third        34

6

È possibile utilizzare la funzione group.sumdal pacchetto 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 ha molte funzioni di gruppo edgroup.sumè una di queste.


4

usando castinvece di recast(nota 'Frequency'è ora '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)

ottenere:

Category (all)
First     30
Second    5
Third     34

2

Un'altra soluzione che restituisce somme per gruppi in una matrice o in un frame di dati ed è breve e veloce:

rowsum(x$Frequency, x$Category)

Bene, e davvero veloce.
jay.sf,

0

Da allora dplyr 1.0.0, la across()funzione potrebbe essere utilizzata:

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

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

Se interessati a più variabili:

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

E la selezione delle variabili usando gli helper selezionati:

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

Dati di esempio:

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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.