Jak zsumować zmienną według grupy

357

Mam ramkę danych z dwiema kolumnami. Pierwsza kolumna zawiera kategorie takie jak „Pierwsza”, „Druga”, „Trzecia”, a druga kolumna zawiera liczby reprezentujące liczbę wyświetleń określonych grup z „Kategorii”.

Na przykład:

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

Chcę posortować dane według kategorii i zsumować wszystkie częstotliwości:

Category     Frequency
First        30
Second       5
Third        34

Jak mam to zrobić w R?

użytkownik5243421
źródło
1
Najszybszy sposób w bazie R to rowsum.
Michael M

Odpowiedzi:

387

Używanie aggregate:

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

W powyższym przykładzie można określić wiele wymiarów w list. Wiele zagregowanych danych tego samego typu danych można włączyć za pomocą cbind:

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

(osadzanie komentarza @ thelatemail), aggregatema również interfejs formuły

aggregate(Frequency ~ Category, x, sum)

Lub jeśli chcesz agregować wiele kolumn, możesz użyć .notacji (działa również dla jednej kolumny)

aggregate(. ~ Category, x, sum)

lub tapply:

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

Korzystanie z tych danych:

x <- data.frame(Category=factor(c("First", "First", "First", "Second",
                                      "Third", "Third", "Second")), 
                    Frequency=c(10,15,5,2,14,20,3))
rcs
źródło
4
@AndrewMcKinlay, R używa tyldy do definiowania formuł symbolicznych, do celów statystycznych i innych funkcji. Można to interpretować jako „częstotliwość modelu według kategorii” lub „częstotliwość w zależności od kategorii” . Nie wszystkie języki używają specjalnego operatora do zdefiniowania funkcji symbolicznej, tak jak w R tutaj. Być może dzięki „naturalnej interpretacji” operatora tylda staje się on bardziej znaczący (a nawet intuicyjny). Osobiście uważam tę symboliczną reprezentację formuły za lepszą niż niektóre bardziej szczegółowe alternatywy.
r2evans,
1
Będąc nowym w R (i zadającym te same pytania, co OP), skorzystałbym z bardziej szczegółowych informacji na temat składni każdej alternatywy. Na przykład, jeśli mam większą tabelę źródłową i chcę podselekcjonować tylko dwa wymiary plus zsumowane dane, czy mogę dostosować którąkolwiek z tych metod? Ciężko powiedzieć.
Dodekafon
236

Możesz również użyć do tego celu pakietu 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

Lub w przypadku wielu kolumn podsumowań (działa również z jedną kolumną):

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

Oto kilka innych przykładów podsumowywania danych według grup przy użyciu funkcji dplyr przy użyciu wbudowanego zestawu danych 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))

Aby uzyskać więcej informacji, w tym %>%operatora, zobacz wprowadzenie do dplyr .

talat
źródło
1
Jak szybko to jest w porównaniu z tabelą data.tab i zagregowanymi alternatywami przedstawionymi w innych odpowiedziach?
asieira
5
@asieira, która jest najszybsza i jak duża jest różnica (lub jeśli różnica jest zauważalna), zawsze będzie zależeć od wielkości danych. Zazwyczaj w przypadku dużych zestawów danych, na przykład niektórych GB, data.table najprawdopodobniej będzie najszybsza. W przypadku mniejszych rozmiarów danych data.table i dplyr są często blisko, również w zależności od liczby grup. Zarówno dane, tabela, jak i dplyr będą jednak znacznie szybsze niż funkcje podstawowe (w niektórych operacjach mogą być 100-1000 razy szybsze). Zobacz także tutaj
talat
1
Do czego odnoszą się „funs” w drugim przykładzie?
lauren.marietta
@ lauren.marietta możesz określić funkcje, które chcesz zastosować jako podsumowanie w funs()argumencie summarise_alli powiązane z nim funkcje ( summarise_at, summarise_if)
talat
76

Odpowiedź udzielona przez rcs działa i jest prosta. Jeśli jednak operujesz większymi zestawami danych i potrzebujesz zwiększenia wydajności, istnieje szybsza alternatywa:

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 

Porównajmy to do tej samej rzeczy przy użyciu data.frame i powyższego:

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 

A jeśli chcesz zachować kolumnę, jest to składnia:

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

Różnica stanie się bardziej zauważalna przy większych zestawach danych, jak pokazuje poniższy kod:

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 

W przypadku wielu agregacji można łączyć lapplyi .SDw następujący sposób

data[, lapply(.SD, sum), by = Category]
#    Category Frequency
# 1:    First        30
# 2:   Second         5
# 3:    Third        34
asieira
źródło
13
+1 Ale 0,296 vs 0,059 nie jest szczególnie imponujące. Rozmiar danych musi być znacznie większy niż 300 tys. Wierszy i mieć więcej niż 3 grupy, aby tabela danych świeciła. Na przykład wkrótce postaramy się obsłużyć ponad 2 miliardy wierszy, ponieważ niektórzy użytkownicy tabeli danych mają 250 GB pamięci RAM, a GNU R obsługuje teraz długość> 2 ^ 31.
Matt Dowle,
2
Prawdziwe. Okazuje się, że nie mam całej pamięci RAM i po prostu starałem się dostarczyć dowody na lepszą wydajność data.table. Jestem pewien, że różnica byłaby jeszcze większa przy większej ilości danych.
asieira
1
Miałem 7 milionów obserwacji dplyr zajęło 0,3 sekundy, a agregacja () zajęła 22 sekundy, aby zakończyć operację. Zamierzałem opublikować to na ten temat, a ty mnie do tego pobiłaś!
zazu
3
Jest jeszcze krótszy sposób na napisanie tego data[, sum(Frequency), by = Category]. Możesz użyć, .Nktóry zastępuje sum()funkcję. data[, .N, by = Category]. Oto przydatny ściągawka: s3.amazonaws.com/assets.datacamp.com/img/blog/…
Stophface
3
Użycie .N będzie równoznaczne z sumą (Częstotliwość) tylko wtedy, gdy wszystkie wartości w kolumnie Częstotliwość będą równe 1, ponieważ .N zlicza liczbę wierszy w każdym zbiorczym zestawie (.SD). I tak nie jest tutaj.
asieira
41

Możesz także użyć funkcji by () :

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

Te inne pakiety (plyr, reshape) mają tę zaletę, że zwracają data.frame, ale warto zapoznać się z (), ponieważ jest to funkcja podstawowa.

Shane
źródło
28

Kilka lat później, aby dodać kolejne proste podstawowe rozwiązanie R, które nie jest tutaj obecne z jakiegoś powodu- xtabs

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

Lub jeśli chcesz data.frameplecy

as.data.frame(xtabs(Frequency ~ Category, df))
#   Category Freq
# 1    First   30
# 2   Second    5
# 3    Third   34
David Arenburg
źródło
27
library(plyr)
ddply(tbl, .(Category), summarise, sum = sum(Frequency))
uczący się
źródło
23

Jeśli xjest to ramka danych z Twoimi danymi, następujące czynności zrobią, co chcesz:

require(reshape)
recast(x, Category ~ ., fun.aggregate=sum)
Rob Hyndman
źródło
19

Chociaż ostatnio stałem się konwertowany dplyrna większość tego typu operacji, tosqldf pakiet jest nadal bardzo ładny (i IMHO bardziej czytelny) dla niektórych rzeczy.

Oto przykład, w jaki sposób można odpowiedzieć na to pytanie 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
źródło
18

Aby dodać trzecią opcję:

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

EDYCJA: to bardzo stara odpowiedź. Teraz poleciłbym użycie group_byi summariseod dplyr, jak w odpowiedzi @docendo.

dalloliogm
źródło
7

znajduję ave bardzo pomocny (i wydajny), gdy trzeba zastosować różne funkcje agregacji w różnych kolumnach (i musisz / chcesz trzymać się podstawy R):

na przykład

Biorąc pod uwagę ten wkład:

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

chcemy przez grupy Categ1i Categ2i obliczyć sumę Samplesi średnio Freq.
Oto możliwe rozwiązanie za pomocą 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),]

Wynik:

> 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
źródło
6

Ostatnio dodane dplyr::tally()teraz sprawia, że ​​jest to łatwiejsze niż kiedykolwiek:

tally(x, Category)

Category     n
First        30
Second       5
Third        34
dmca
źródło
6

Możesz użyć funkcji group.sumz pakietu 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 ma wiele funkcji grupowych igroup.sumjest jedną z nich.

Manos Papadakis
źródło
4

używanie castzamiast recast(uwaga 'Frequency'jest teraz '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)

uzyskać:

Category (all)
First     30
Second    5
Third     34
Grant Shannon
źródło
2

Inne rozwiązanie, które zwraca sumy według grup w macierzy lub ramce danych i jest krótkie i szybkie:

rowsum(x$Frequency, x$Category)
Karolis Koncevičius
źródło
Ładnie i rzeczywiście szybko.
jay.sf
0

Ponieważ dplyr 1.0.0The across()funkcja może być używana:

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

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

W przypadku zainteresowania wieloma zmiennymi:

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

A wybór zmiennych za pomocą select helpers:

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

Przykładowe dane:

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
źródło