Cómo sumar una variable por grupo

357

Tengo un marco de datos con dos columnas. La primera columna contiene categorías como "Primero", "Segundo", "Tercero", y la segunda columna tiene números que representan la cantidad de veces que vi los grupos específicos de "Categoría".

Por ejemplo:

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

Quiero ordenar los datos por categoría y sumar todas las frecuencias:

Category     Frequency
First        30
Second       5
Third        34

¿Cómo haría esto en R?

usuario5243421
fuente
1
La forma más rápida en la base R es rowsum.
Michael M

Respuestas:

387

Utilizando aggregate:

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

En el ejemplo anterior, se pueden especificar varias dimensiones en list. Se pueden incorporar múltiples métricas agregadas del mismo tipo de datos a través de cbind:

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

(incrustando el comentario de @thelatemail), también aggregatetiene una interfaz de fórmula

aggregate(Frequency ~ Category, x, sum)

O si desea agregar varias columnas, puede usar la .notación (también funciona para una columna)

aggregate(. ~ Category, x, sum)

o tapply:

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

Usando estos datos:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))
rcs
fuente
44
@AndrewMcKinlay, R usa la tilde para definir fórmulas simbólicas, para estadísticas y otras funciones. Se puede interpretar como "frecuencia de modelo por categoría" o "frecuencia según la categoría" . No todos los lenguajes usan un operador especial para definir una función simbólica, como se hace en R aquí. Quizás con esa "interpretación en lenguaje natural" del operador tilde, se vuelve más significativo (e incluso intuitivo). Personalmente, encuentro esta representación de fórmula simbólica mejor que algunas de las alternativas más detalladas.
r2evans
1
Al ser nuevo en R (y hacer el mismo tipo de preguntas que el OP), me beneficiaría de algunos detalles más de la sintaxis detrás de cada alternativa. Por ejemplo, si tengo una tabla fuente más grande y quiero subseleccionar solo dos dimensiones más métricas sumadas, ¿puedo adaptar alguno de estos métodos? Difícil de decir.
Dodecaphone
236

También puede usar el paquete dplyr para ese propósito:

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

O, para varias columnas de resumen (también funciona con una columna):

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

Aquí hay algunos ejemplos más de cómo resumir datos por grupo usando las funciones dplyr usando el conjunto de datos incorporado 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 obtener más información, incluido el %>%operador, consulte la introducción a dplyr .

talat
fuente
1
¿Qué tan rápido es en comparación con las alternativas data.table y agregadas presentadas en otras respuestas?
asieira
55
@asieira, lo que es más rápido y qué tan grande es la diferencia (o si la diferencia es notable) siempre dependerá de su tamaño de datos. Por lo general, para grandes conjuntos de datos, por ejemplo, algunos GB, data.table probablemente será el más rápido. En un tamaño de datos más pequeño, data.table y dplyr a menudo están cerca, también dependiendo del número de grupos. Sin embargo, los datos, la tabla y el dplyr serán mucho más rápidos que las funciones básicas (puede ser de 100 a 1000 veces más rápido para algunas operaciones). También vea aquí
talat
1
¿A qué se refieren las "diversiones" en el segundo ejemplo?
lauren.marietta
@ lauren.marietta puede especificar las funciones que desea aplicar como resumen dentro del funs()argumento summarise_ally sus funciones relacionadas ( summarise_at, summarise_if)
talat
76

La respuesta proporcionada por rcs funciona y es simple. Sin embargo, si está manejando conjuntos de datos más grandes y necesita un aumento de rendimiento, existe una alternativa más 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 

Comparemos eso con lo mismo usando data.frame y lo anterior arriba:

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 

Y si desea mantener la columna, esta es la sintaxis:

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

La diferencia se hará más notable con conjuntos de datos más grandes, como lo demuestra el siguiente código:

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 múltiples agregaciones, puede combinar lapplyy de la .SDsiguiente manera

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
asieira
fuente
13
+1 Pero 0.296 vs 0.059 no es particularmente impresionante. El tamaño de los datos debe ser mucho mayor que 300k filas, y con más de 3 grupos, para que data.table brille. Intentaremos admitir más de 2 mil millones de filas pronto, por ejemplo, ya que algunos usuarios de data.table tienen 250 GB de RAM y GNU R ahora admite longitud> 2 ^ 31.
Matt Dowle
2
Cierto. Sin embargo, no tengo toda esa RAM, y simplemente estaba tratando de proporcionar alguna evidencia del rendimiento superior de data.table. Estoy seguro de que la diferencia sería aún mayor con más datos.
asieira
1
Tuve 7 mil observaciones dplyr tardó 0,3 segundos y agregado () tardó 22 segundos para completar la operación. ¡Lo iba a publicar sobre este tema y me ganaste!
zazu
3
Hay una forma aún más corta de escribir esto data[, sum(Frequency), by = Category]. Podrías usar .Nque sustituye a la sum()función. data[, .N, by = Category]. Aquí hay una hoja de trucos útil: s3.amazonaws.com/assets.datacamp.com/img/blog/…
Stophface
3
Usar .N sería equivalente a la suma (Frecuencia) solo si todos los valores en la columna Frecuencia fueran iguales a 1, porque .N cuenta el número de filas en cada conjunto agregado (.SD). Y ese no es el caso aquí.
asieira
41

También puede usar la función by () :

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

Esos otros paquetes (plyr, rehape) tienen la ventaja de devolver un data.frame, pero vale la pena estar familiarizado con by () ya que es una función base.

Shane
fuente
28

Varios años después, solo para agregar otra solución base R simple que no está presente aquí por alguna razón: xtabs

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

O si quieres un data.framerespaldo

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

Si se xtrata de un marco de datos con sus datos, lo siguiente hará lo que desee:

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

Si bien recientemente me convertí dplyrpara la mayoría de estos tipos de operaciones, el sqldfpaquete sigue siendo realmente agradable (y en mi humilde opinión, más legible) para algunas cosas.

Aquí hay un ejemplo de cómo se puede responder esta pregunta con 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
fuente
18

Solo para agregar una tercera opción:

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

EDITAR: esta es una respuesta muy antigua. Ahora recomendaría el uso de group_byy summarisedesde dplyr, como en @docendo respuesta.

dalloliogm
fuente
7

Me parece avemuy útil (y eficiente) cuando necesita aplicar diferentes funciones de agregación en diferentes columnas (y debe / desea pegarse en la base R):

p.ej

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 Categ1y Categ2y calcular la suma de Samplesy media de Freq.
Aquí hay una posible solución 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
fuente
6

El agregado recientemente dplyr::tally()ahora hace que esto sea más fácil que nunca:

tally(x, Category)

Category     n
First        30
Second       5
Third        34
dmca
fuente
6

Puede usar la función group.sumdel paquete 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 tiene muchas funciones de grupo ygroup.sumes una de ellas.

Manos Papadakis
fuente
4

usando en castlugar de recast(la nota 'Frequency'es ahora '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)

Llegar:

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

Otra solución que devuelve sumas por grupos en una matriz o un marco de datos y es corta y rápida:

rowsum(x$Frequency, x$Category)
Karolis Koncevičius
fuente
Muy bien, y de hecho rápido.
jay.sf
0

Desde entonces dplyr 1.0.0, la across()función podría usarse:

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

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

Si está interesado en múltiples 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

Y la selección de variables con ayudantes seleccionados:

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

Data de muestra:

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
fuente