Как суммировать переменную по группе - PullRequest
298 голосов
/ 02 ноября 2009

Допустим, у меня есть два столбца данных. Первый содержит такие категории, как «Первый», «Второй», «Третий» и т. Д. Второй содержит числа, которые представляют количество раз, которое я видел «Первый».

Например:

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

Я хочу отсортировать данные по категориям и суммировать частоты:

Category     Frequency
First        30
Second       5
Third        34

Как бы я это сделал в R?

Ответы [ 13 ]

332 голосов
/ 02 ноября 2009

Использование aggregate:

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

В приведенном выше примере в list можно указать несколько измерений. Несколько агрегированных показателей одного и того же типа данных могут быть включены через cbind:

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

(вложение комментария @thelatemail), aggregate также имеет интерфейс формулы

aggregate(Frequency ~ Category, x, sum)

Или, если вы хотите объединить несколько столбцов, вы можете использовать обозначение . (работает и для одного столбца)

aggregate(. ~ Category, x, sum)

или tapply:

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

Используя эти данные:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))
181 голосов
/ 03 декабря 2014

В последнее время вы также можете использовать пакет dplyr для этой цели:

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

Или, для несколько сводных столбцов (работает также с одним столбцом):

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

Обновление для dplyr> = 0.5: summarise_each заменено на семейство функций summarise_all, summarise_at и summarise_if в dplyr.

Или, если у вас есть несколько столбцов для группировки, вы можете указать все из них в group_by, разделенных запятыми:

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

Для получения дополнительной информации, включая оператор %>%, см. Введение в dplyr .

65 голосов
/ 08 сентября 2013

Ответ, предоставленный rcs, работает и прост. Однако, если вы обрабатываете большие наборы данных и нуждаетесь в повышении производительности, есть более быстрая альтернатива:

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 

Давайте сравним это с той же вещью, используя data.frame и выше:

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 

А если вы хотите сохранить столбец, это синтаксис:

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

Различие станет более заметным с большими наборами данных, как показано в коде ниже:

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 

Для нескольких агрегаций вы можете объединить lapply и .SD следующим образом

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
36 голосов
/ 02 ноября 2009

Это несколько связано с этим вопросом .

Вы также можете просто использовать функцию by () :

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

Эти другие пакеты (plyr, reshape) имеют преимущество, заключающееся в возврате data.frame, но с ним следует ознакомиться, поскольку это базовая функция.

25 голосов
/ 02 ноября 2009
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
23 голосов
/ 10 сентября 2015

Несколько лет спустя, просто чтобы добавить еще одно простое решение base R, которого по какой-то причине здесь нет - xtabs

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

Или, если вы хотите data.frame назад

as.data.frame(xtabs(Frequency ~ Category, df))
#   Category Freq
# 1    First   30
# 2   Second    5
# 3    Third   34
18 голосов
/ 02 ноября 2009

Если x - это фрейм данных с вашими данными, то следующее будет делать то, что вы хотите:

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
16 голосов
/ 17 мая 2016

Хотя я недавно стал преобразовывать в dplyr для большинства операций такого типа, пакет sqldf все еще действительно хорош (и ИМХО более читабелен) для некоторых вещей.

Вот пример того, как на этот вопрос можно ответить с помощью 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 голосов
/ 02 ноября 2009

Просто добавьте третий вариант:

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

РЕДАКТИРОВАТЬ: это очень старый ответ. Теперь я бы рекомендовал использовать group_by и summarise из dplyr, как в @docendo answer.

4 голосов
/ 10 декабря 2018

Я считаю ave очень полезным (и эффективным), когда вам нужно применить различные функции агрегирования к разным столбцам (и вы должны / хотите придерживаться базы R):

, например

С учетом этого ввода:

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

мы хотим сгруппировать по Categ1 и Categ2 и вычислить сумму Samples и среднее значение Freq.
Вот возможное решение с использованием 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),]

Результат:

> 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
...