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

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

Например:

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?


person user5243421    schedule 02.11.2009    source источник
comment
Самый быстрый способ в базе R - rowsum.   -  person Michael M    schedule 04.01.2019


Ответы (13)


Использование 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)

or 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))
person rcs    schedule 02.11.2009
comment
@AndrewMcKinlay, R использует тильду для определения символьных формул, статистики и других функций. Его можно интерпретировать как частота модели по категории или частота в зависимости от категории. Не все языки используют специальный оператор для определения символьной функции, как это сделано в R здесь. Возможно, с такой интерпретацией оператора тильды на естественном языке он станет более значимым (и даже интуитивно понятным). Я лично считаю, что это символическое представление формулы лучше, чем некоторые из более подробных альтернатив. - person r2evans; 19.12.2016
comment
Поскольку я новичок в R (и задаю те же вопросы, что и OP), мне было бы полезно получить более подробную информацию о синтаксисе каждой альтернативы. Например, если у меня большая исходная таблица, и я хочу выделить только два измерения плюс суммарные показатели, могу ли я адаптировать любой из этих методов? Трудно сказать. - person Dodecaphone; 28.10.2018
comment
Есть ли способ сохранить столбец идентификатора? Скажем, категории упорядочены, а столбец идентификатора - 1:nrow(df), можно ли сохранить начальную позицию каждой категории после агрегирования? Таким образом, столбец идентификатора будет иметь, например, 1, 3, 4, 7 после свертывания с помощью агрегата. В моем случае мне нравится aggregate, потому что он автоматически работает со многими столбцами. - person QAsena; 24.06.2020

Для этой цели также можно использовать пакет 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(across(everything(), sum))

Вот еще несколько примеров того, как суммировать данные по группам с помощью функций dplyr с использованием встроенного набора данных 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(across(everything(), sum))

# summarise all columns except grouping columns using "sum" and "mean"
mtcars %>% 
  group_by(cyl) %>% 
  summarise(across(everything(), list(mean = mean, sum = sum)))

# multiple grouping columns
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise(across(everything(), list(mean = mean, sum = sum)))

# summarise specific variables, not all
mtcars %>% 
  group_by(cyl, gear) %>% 
  summarise(across(c(qsec, mpg, wt), list(mean = mean, sum = sum)))

# summarise specific variables (numeric columns except grouping columns)
mtcars %>% 
  group_by(gear) %>% 
  summarise(across(where(is.numeric), list(mean = mean, sum = sum)))

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

person talat    schedule 03.12.2014
comment
Насколько быстро это по сравнению с data.table и агрегированными альтернативами, представленными в других ответах? - person asieira; 23.01.2015
comment
На что ссылаются funs во втором примере? - person lauren.marietta; 08.10.2019
comment
@ lauren.marietta вы можете указать функции, которые вы хотите применить, в качестве сводки внутри funs() аргумента summarise_all и связанных с ним функций (summarise_at, summarise_if) - person talat; 09.10.2019
comment
В случае, если в названии столбца есть пробелы. Это может не сработать. Использование обратных клещей поможет. Ref. stackoverflow.com/questions/22842232/ - person user131476; 02.11.2020

Ответ, предоставленный 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
person asieira    schedule 08.09.2013
comment
+1 А вот 0,296 против 0,059 особо не впечатляет. Размер данных должен быть намного больше, чем 300 тыс. Строк, и иметь более 3 групп, чтобы data.table сиял. Например, в ближайшее время мы попытаемся поддержать более 2 миллиардов строк, поскольку некоторые пользователи data.table имеют 250 ГБ ОЗУ, а GNU R теперь поддерживает длину ›2 ^ 31. - person Matt Dowle; 09.09.2013
comment
Правда. Оказалось, что у меня нет всей этой оперативной памяти, и я просто пытался предоставить некоторые доказательства превосходной производительности data.table. Я уверен, что при большем количестве данных разница была бы еще больше. - person asieira; 24.10.2013
comment
У меня было 7 миллионов наблюдений, dplyr занял 0,3 секунды, а aggregate () - 22 секунды, чтобы завершить операцию. Я собирался опубликовать это по этой теме, и вы меня опередили! - person zazu; 14.11.2015
comment
Есть еще более короткий способ написать это data[, sum(Frequency), by = Category]. Вы можете использовать .N, который заменяет функцию sum(). data[, .N, by = Category]. Вот полезная шпаргалка: s3. amazonaws.com/assets.datacamp.com/img/blog/ - person Stophface; 22.02.2017
comment
Использование .N было бы эквивалентно sum (Frequency), только если бы все значения в столбце Frequency были равны 1, потому что .N подсчитывает количество строк в каждом агрегированном наборе (.SD). А здесь дело обстоит иначе. - person asieira; 01.03.2017

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

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

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

person Shane    schedule 02.11.2009

Несколько лет спустя, просто чтобы добавить еще одно простое базовое решение 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
person David Arenburg    schedule 10.09.2015

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

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
person Rob Hyndman    schedule 02.11.2009

Хотя я недавно перешел на 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
person joemienko    schedule 17.05.2016

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

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

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

person dalloliogm    schedule 02.11.2009

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

e.g.

Учитывая этот ввод:

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
person digEmAll    schedule 10.12.2018

Другое короткое и быстрое решение, которое возвращает суммы по группам в матрице или фрейме данных:

rowsum(x$Frequency, x$Category)
person Karolis Koncevičius    schedule 28.04.2020
comment
Красиво и действительно быстро. - person jay.sf; 02.05.2020

Начиная с dplyr 1.0.0, можно использовать функцию across():

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

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

Если вас интересует несколько переменных:

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

И выбор переменных с помощью помощников выбора:

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

Образец данных:

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)
person tmfmnk    schedule 14.06.2020

Вы можете использовать функцию group.sum из пакета 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 имеет множество групповых функций, и group.sum является одной из них.

person Manos Papadakis    schedule 18.11.2018

с использованием cast вместо recast (примечание '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
person Grant Shannon    schedule 25.02.2018