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

357

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

Например:

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?

user5243421
источник
1
Самый быстрый способ в базе R есть rowsum.
Майкл М

Ответы:

387

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

Для этого вы также можете использовать пакет 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_all(funs(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_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))

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

Талаат
источник
1
Насколько быстро это по сравнению с таблицей data.table и совокупными альтернативами, представленными в других ответах?
asieira
5
@asieira, который самый быстрый и насколько велика разница (или если разница заметна) всегда будет зависеть от размера ваших данных. Как правило, для больших наборов данных, например некоторых ГБ, data.table, скорее всего, будет самым быстрым. При меньшем размере данных data.table и dplyr часто близки, также в зависимости от количества групп. Однако данные, таблица и dplyr будут работать намного быстрее, чем базовые функции (для некоторых операций они могут быть в 100-1000 раз быстрее). Также смотрите здесь
Талат
1
Что означает «веселье» во втором примере?
lauren.marietta
@ lauren.marietta вы можете указать функции, которые вы хотите применить в качестве сводки внутри funs()аргумента summarise_allи связанных с ним функций ( summarise_at, summarise_if)
talat
76

Ответ, предоставленный 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
asieira
источник
13
+1 Но 0,296 против 0,059 не особо впечатляет. Размер данных должен быть намного больше, чем 300 тыс. Строк, и иметь более 3 групп, чтобы data.table мог блестеть. Например, в ближайшее время мы попытаемся поддержать более 2 миллиардов строк, поскольку некоторые пользователи data.table имеют 250 ГБ ОЗУ, а GNU R теперь поддерживает длину> 2 ^ 31.
Мэтт Доул
2
Правда. Оказывается, у меня нет всей этой оперативной памяти, и я просто пытался предоставить некоторые доказательства превосходной производительности data.table. Я уверен, что разница будет еще больше с большим количеством данных.
asieira
1
У меня было 7 миллионов наблюдений. Dplyr занял 0,3 секунды, а агрегат () занял 22 секунды, чтобы завершить операцию. Я собирался опубликовать это на эту тему, и вы победили меня в этом!
Zazu
3
Есть еще более короткий способ написать это data[, sum(Frequency), by = Category]. Вы можете использовать, .Nкоторый заменяет sum()функцию. data[, .N, by = Category], Вот полезная шпаргалка: s3.amazonaws.com/assets.datacamp.com/img/blog/...
Stophface
3
Использование .N было бы эквивалентно сумме (Частоте), только если все значения в столбце Частота были равны 1, потому что .N подсчитывает количество строк в каждом агрегированном наборе (.SD). И это не тот случай, здесь.
Asieira
41

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

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

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

Шейн
источник
28

Несколько лет спустя, просто чтобы добавить еще одно простое решение 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
Дэвид Аренбург
источник
27
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
learnr
источник
23

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

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
Роб Хиндман
источник
19

Несмотря на то, что недавно я стал конвертировать в 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
joemienko
источник
18

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

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

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

dalloliogm
источник
7

я нахожу 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
digEmAll
источник
6

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

tally(x, Category)

Category     n
First        30
Second       5
Third        34
DMCA
источник
6

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

Манос Пападакис
источник
4

используя 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
Грант Шеннон
источник
2

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

rowsum(x$Frequency, x$Category)
Каролис Концевичюс
источник
Красиво и действительно быстро.
jay.sf
0

Так 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)
tmfmnk
источник