Как суммировать переменную по группе?

278

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

Например:

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?

  • 1
    Самый быстрый способ в базе R - это rowsum .
Теги:
sorting
r-faq

13 ответов

276
Лучший ответ

Использование 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))
  • 4
    @AndrewMcKinlay, R использует тильду для определения символьных формул, для статистики и других функций. Его можно интерпретировать как «Частота модели по категории» или «Частота в зависимости от категории» . Не все языки используют специальный оператор для определения символической функции, как это сделано в R здесь. Возможно, с такой «интерпретацией на естественном языке» оператора тильды это становится более значимым (и даже интуитивным). Я лично нахожу это символическое представление формул лучше, чем некоторые из более многословных альтернатив.
  • 1
    Будучи новичком в R (и задавая те же вопросы, что и OP), я бы выиграл от некоторых подробностей синтаксиса каждой альтернативы. Например, если у меня есть исходная таблица большего размера и я хочу выбрать только два измерения плюс суммированные метрики, могу ли я адаптировать любой из этих методов? Трудно сказать.
152

Совсем недавно вы также можете использовать пакет 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.

  • 1
    Насколько быстро это по сравнению с таблицей data.table и совокупными альтернативами, представленными в других ответах?
  • 4
    @asieira, который самый быстрый и насколько велика разница (или если разница заметна) всегда будет зависеть от размера ваших данных. Как правило, для больших наборов данных, например некоторых ГБ, data.table, скорее всего, будет самым быстрым. При меньшем размере данных data.table и dplyr часто близки, также в зависимости от количества групп. Однако данные, таблица и dplyr будут намного быстрее, чем базовые функции (для некоторых операций они могут быть в 100-1000 раз быстрее). Также смотрите здесь
60

Ответ, предоставленный 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
  • 11
    +1 Но 0,296 против 0,059 не особо впечатляет. Размер данных должен быть намного больше, чем 300 тыс. Строк, и иметь более 3 групп, чтобы data.table мог сиять. Например, в ближайшее время мы попытаемся поддержать более 2 миллиардов строк, поскольку некоторые пользователи data.table имеют 250 ГБ ОЗУ, а GNU R теперь поддерживает длину> 2 ^ 31.
  • 2
    Правда. Оказывается, у меня нет всей этой оперативной памяти, и я просто пытался предоставить некоторые доказательства превосходной производительности data.table. Я уверен, что разница будет еще больше с большим количеством данных.
Показать ещё 3 комментария
35

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

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

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

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

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

Несколько лет спустя просто добавить еще одно простое базовое решение 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
16

В то время как я недавно стал конвертировать в dplyr для большинства этих типов операций, пакет sqldf по-прежнему очень хорош (и IMHO более читабельным) для некоторых вещей.

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

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

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

EDIT: это очень старый ответ. Теперь я бы рекомендовал использовать group_by и суммировать из dplyr, как в ответе @docendo.

16

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

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

Вы можете использовать функцию group.sum из пакета Rfast.

Category <- Rfast::as_integer(Category,result.sort=FALSE) # convert character to numeric. R as.numeric produce NAs.
result <- Rfast::group.sum(Frequency,Category)
names(result) <- Rfast::Sort(unique(Category)
# 30 5 34

Rfast имеет много групповых функций, и group.sum является одной из них.

2

используя cast вместо recast (note 'Frequency' теперь '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)

чтобы получить:

Category (all)
First     30
Second    5
Third     34
1

Я считаю, 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
0

Недавно добавленный dplyr::tally() теперь делает это проще, чем когда-либо:

tally(x, Category)

Category     n
First        30
Second       5
Third        34

Ещё вопросы

Сообщество Overcoder
Наверх
Меню