Como somar uma variável por grupo

357

Eu tenho um quadro de dados com duas colunas. A primeira coluna contém categorias como "Primeiro", "Segundo", "Terceiro" e a segunda coluna possui números que representam o número de vezes que vi os grupos específicos de "Categoria".

Por exemplo:

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

Quero classificar os dados por categoria e somar todas as frequências:

Category     Frequency
First        30
Second       5
Third        34

Como eu faria isso em R?

user5243421
fonte
11
A maneira mais rápida na base R é rowsum.
Michael M

Respostas:

387

Usando aggregate:

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

No exemplo acima, várias dimensões podem ser especificadas no list. Várias métricas agregadas do mesmo tipo de dados podem ser incorporadas via cbind:

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

(incorporando @thelatemail comment), também aggregatepossui uma interface de fórmula

aggregate(Frequency ~ Category, x, sum)

Ou, se você quiser agregar várias colunas, poderá usar a .notação (funciona também para uma coluna)

aggregate(. ~ Category, x, sum)

ou tapply:

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

Usando estes dados:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))
rcs
fonte
4
@AndrewMcKinlay, R usa o til para definir fórmulas simbólicas, para estatísticas e outras funções. Pode ser interpretado como "frequência do modelo por categoria" ou "frequência dependendo da categoria" . Nem todos os idiomas usam um operador especial para definir uma função simbólica, como feito em R aqui. Talvez com essa "interpretação em linguagem natural" do operador til, ela se torne mais significativa (e até intuitiva). Pessoalmente, acho melhor essa representação simbólica da fórmula do que algumas das alternativas mais detalhadas.
R2evans
11
Sendo novo no R (e fazendo o mesmo tipo de perguntas que o OP), eu me beneficiaria de mais alguns detalhes da sintaxe por trás de cada alternativa. Por exemplo, se eu tiver uma tabela de origem maior e quiser selecionar novamente apenas duas dimensões mais métricas somadas, posso adaptar algum desses métodos? Difícil de dizer.
Dodecaphone
236

Você também pode usar o pacote dplyr para esse fim:

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, para várias colunas de resumo (também funciona com uma coluna):

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

Aqui estão mais alguns exemplos de como resumir dados por grupo usando funções dplyr usando o conjunto de dados interno 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))

Para mais informações, incluindo o %>%operador, consulte a introdução ao dplyr .

talat
fonte
11
Qual é a velocidade quando comparado às alternativas data.table e agregadas apresentadas em outras respostas?
Asieira
5
@asieira, que é mais rápida e qual o tamanho da diferença (ou se a diferença é perceptível) sempre dependerá do tamanho dos dados. Normalmente, para grandes conjuntos de dados, por exemplo, alguns GB, a data.table provavelmente será a mais rápida. Em um tamanho de dados menor, data.table e dplyr geralmente estão próximos, também dependendo do número de grupos. Entretanto, dados, tabela e dplyr serão muito mais rápidos que as funções base (no entanto, pode ser 100-1000 vezes mais rápido em algumas operações). Veja também aqui
talat
11
A que os "divertimentos" se referem no segundo exemplo?
Lauren.marietta 8/08/19
@ lauren.marietta você pode especificar a função (s) que deseja aplicar como resumo dentro do funs()argumento summarise_alle suas funções relacionadas ( summarise_at, summarise_if)
talat
76

A resposta fornecida pelo rcs funciona e é simples. No entanto, se você estiver lidando com conjuntos de dados maiores e precisar de um aumento de desempenho, há uma alternativa mais rápida:

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 

Vamos comparar isso com a mesma coisa usando data.frame e acima:

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 você deseja manter a coluna, esta é a sintaxe:

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

A diferença se tornará mais visível com conjuntos de dados maiores, como o código abaixo demonstra:

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 

Para várias agregações, você pode combinar lapplye da .SDseguinte maneira

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
asieira
fonte
13
+1 Mas 0.296 vs 0.059 não é particularmente impressionante. O tamanho dos dados precisa ser muito maior que 300k linhas e com mais de 3 grupos para que a tabela de dados brilhe. Em breve, tentaremos oferecer suporte a mais de 2 bilhões de linhas, por exemplo, já que alguns usuários da data.table têm 250 GB de RAM e o GNU R agora suporta comprimento> 2 ^ 31.
quer
2
Verdade. Acontece que eu não tenho toda essa memória RAM e estava simplesmente tentando fornecer alguma evidência do desempenho superior do data.table. Tenho certeza de que a diferença seria ainda maior com mais dados.
Asieira
11
Eu tinha 7 mil observações. O dplyr levou 0,3 segundos e o agregado () levou 22 segundos para concluir a operação. Eu ia postar sobre esse assunto e você me venceu!
Zazu
3
Existe uma maneira ainda mais curta de escrever isso data[, sum(Frequency), by = Category]. Você pode usar o .Nque substitui a sum()função. data[, .N, by = Category]. Aqui está uma folha de
dicas
3
Usar .N seria equivalente a sum (Frequency) apenas se todos os valores na coluna Frequency fossem iguais a 1, porque .N conta o número de linhas em cada conjunto agregado (.SD). E esse não é o caso aqui.
Asieira #
41

Você também pode usar a função by () :

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

Esses outros pacotes (plyr, remodelar) têm o benefício de retornar um data.frame, mas vale a pena se familiarizar com by (), pois é uma função base.

Shane
fonte
28

Vários anos depois, apenas para adicionar outra solução R básica simples que não está presente aqui por algum motivo - xtabs

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

Ou se você quer uma data.framevolta

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

Se xfor um quadro de dados com seus dados, o seguinte fará o que você deseja:

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

Embora eu tenha me convertido recentemente dplyrpara a maioria desses tipos de operações, o sqldfpacote ainda é muito bom (e IMHO mais legível) para algumas coisas.

Aqui está um exemplo de como essa pergunta pode ser respondida com 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
fonte
18

Apenas para adicionar uma terceira opção:

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

EDIT: esta é uma resposta muito antiga. Agora eu recomendaria o uso de group_bye summarisede dplyr, como na resposta do @docendo.

dalloliogm
fonte
7

Acho avemuito útil (e eficiente) quando você precisa aplicar diferentes funções de agregação em diferentes colunas (e você deve / deseja manter a base R):

por exemplo

Dada esta entrada:

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

queremos agrupar por Categ1e Categ2computar a soma Samplese a média de Freq.
Aqui está uma solução possível 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),]

Resultado:

> 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
fonte
6

O recentemente adicionado dplyr::tally()agora torna isso mais fácil do que nunca:

tally(x, Category)

Category     n
First        30
Second       5
Third        34
dmca
fonte
6

Você pode usar a função group.sumdo pacote 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

O Rfast possui muitas funções de grupo egroup.sumé uma delas.

Manos Papadakis
fonte
4

usando em castvez de recast(note 'Frequency'agora '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)

para obter:

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

Outra solução que retorna somas por grupos em uma matriz ou quadro de dados e é curta e rápida:

rowsum(x$Frequency, x$Category)
Karolis Koncevičius
fonte
Bem, e de fato rápido.
jay.sf
0

Desde então dplyr 1.0.0, a across()função poderia ser usada:

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

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

Se estiver interessado em várias variáveis:

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 a seleção de variáveis ​​usando ajudantes selecionados:

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

Dados de amostra:

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
fonte