Як підсумовувати змінну за групою


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?


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 (і задаючи ті ж самі запитання, що й ОП), я б виграв з детальнішою синтаксисом кожної альтернативи. Наприклад, якщо я маю більшу таблицю вихідних даних і хочу виділити лише два виміри плюс підсумовані показники, чи можу я адаптувати будь-який із цих методів? Важко сказати.
Додекафон

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
Як швидко це порівняно з таблицями даних та сукупними альтернативами, представленими в інших відповідях?
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

13
+1 Але 0,296 проти 0,059 не особливо вражає. Розмір даних повинен бути набагато більшим, ніж 300 тис. Рядків, і з більш ніж 3-х груп, щоб дані.table могли світити. Наприклад, ми скоро спробуємо підтримати більше 2 мільярдів рядків, оскільки деякі користувачі даних.table мають 250 ГБ оперативної пам’яті, а GNU R тепер підтримує довжину> 2 ^ 31.
Метт Даул

2
Правда. Виявляється, я не маю всієї цієї оперативної пам’яті, і просто намагався надати деякі докази про вищу продуктивність data.table. Я впевнений, що різниця буде ще більшою, якщо більше даних.
asieira

1
У мене було 7 мільйонів спостережень, для виконання операції було витрачено 3 секунди, а для завершення операції - 22 секунди. Я збирався опублікувати його на цю тему, і ви мене побили!
зазу

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

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


23

Якщо xє кадр даних із вашими даними, то наступне буде робити те, що ви хочете:

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

19

Хоча я нещодавно став перетворювачем 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

18

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

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

EDIT: це дуже стара відповідь. Тепер я рекомендував би використовувати group_byі summariseвід dplyr, як у відповіді @docendo.


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

6

Нещодавно додане dplyr::tally()зараз робить це простіше, ніж будь-коли:

tally(x, Category)

Category     n
First        30
Second       5
Third        34

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.