Cách tổng hợp một biến theo nhóm


357

Tôi có một khung dữ liệu với hai cột. Cột đầu tiên chứa các danh mục như "Đầu tiên", "Thứ hai", "Thứ ba" và cột thứ hai có các số đại diện cho số lần tôi thấy các nhóm cụ thể từ "Danh mục".

Ví dụ:

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

Tôi muốn sắp xếp dữ liệu theo Danh mục và tổng hợp tất cả các tần suất:

Category     Frequency
First        30
Second       5
Third        34

Làm thế nào tôi có thể làm điều này trong R?


1
Cách nhanh nhất trong cơ sở R là rowsum.
Michael M

Câu trả lời:


387

Sử dụng aggregate:

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

Trong ví dụ trên, nhiều kích thước có thể được chỉ định trong list. Nhiều số liệu tổng hợp của cùng loại dữ liệu có thể được kết hợp thông qua cbind:

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

(nhúng bình luận @thelatemail), aggregatecũng có giao diện công thức

aggregate(Frequency ~ Category, x, sum)

Hoặc nếu bạn muốn tổng hợp nhiều cột, bạn có thể sử dụng .ký hiệu (cũng hoạt động cho một cột)

aggregate(. ~ Category, x, sum)

hoặc tapply:

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

Sử dụng dữ liệu này:

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 sử dụng dấu ngã để xác định các công thức tượng trưng, ​​để thống kê và các chức năng khác. Nó có thể được hiểu là "Tần số mô hình theo Danh mục" hoặc "Tần suất tùy theo Danh mục" . Không phải tất cả các ngôn ngữ đều sử dụng toán tử đặc biệt để xác định hàm biểu tượng, như được thực hiện trong R ở đây. Có lẽ với "diễn giải ngôn ngữ tự nhiên" của toán tử dấu ngã, nó trở nên có ý nghĩa hơn (và thậm chí trực quan). Cá nhân tôi thấy đại diện công thức tượng trưng này tốt hơn so với một số lựa chọn thay thế dài dòng hơn.
r2evans

1
Là người mới đối với R (và hỏi các loại câu hỏi tương tự như OP), tôi sẽ được hưởng lợi từ một số chi tiết hơn về cú pháp đằng sau mỗi phương án. Chẳng hạn, nếu tôi có một bảng nguồn lớn hơn và muốn chọn chỉ hai chiều cộng với các số liệu tổng hợp, tôi có thể điều chỉnh bất kỳ phương thức nào trong số này không? Khó nói.
Dodecaphone

236

Bạn cũng có thể sử dụng gói dplyr cho mục đích đó:

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

Hoặc, đối với nhiều cột tóm tắt (cũng hoạt động với một cột):

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

Dưới đây là một số ví dụ khác về cách tóm tắt dữ liệu theo nhóm bằng cách sử dụng các hàm dplyr bằng cách sử dụng bộ dữ liệu tích hợp 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))

Để biết thêm thông tin, bao gồm cả %>%nhà điều hành, xem phần giới thiệu về dplyr .


1
Nó nhanh như thế nào khi so sánh với các lựa chọn thay thế data.table và tổng hợp được trình bày trong các câu trả lời khác?
asieira

5
@asieira, cái nào nhanh nhất và sự khác biệt lớn như thế nào (hoặc nếu sự khác biệt là đáng chú ý) sẽ luôn phụ thuộc vào kích thước dữ liệu của bạn. Thông thường, đối với các tập dữ liệu lớn, ví dụ như một số GB, data.table rất có thể sẽ nhanh nhất. Trên kích thước dữ liệu nhỏ hơn, data.table và dplyr thường đóng, cũng tùy thuộc vào số lượng nhóm. Tuy nhiên, cả dữ liệu, bảng và dplyr sẽ nhanh hơn khá nhiều so với các hàm cơ sở (tuy nhiên (cũng có thể nhanh hơn 100-1000 lần đối với một số thao tác). Cũng xem tại đây
Talat

1
"Những niềm vui" đề cập đến trong ví dụ thứ hai là gì?
lauren.marietta

@ lauren.marietta bạn có thể chỉ định (các) chức năng bạn muốn áp dụng làm tóm tắt bên trong funs()đối số summarise_allvà các chức năng liên quan của nó ( summarise_at, summarise_if)
Talat

76

Câu trả lời được cung cấp bởi rcs hoạt động và rất đơn giản. Tuy nhiên, nếu bạn đang xử lý các bộ dữ liệu lớn hơn và cần tăng hiệu suất, có một cách khác nhanh hơn:

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 

Hãy so sánh điều đó với cùng một thứ bằng cách sử dụng data.frame và ở trên:

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 

Và nếu bạn muốn giữ cột thì đây là cú pháp:

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

Sự khác biệt sẽ trở nên đáng chú ý hơn với các bộ dữ liệu lớn hơn, vì đoạn mã dưới đây thể hiện:

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 

Đối với nhiều tập hợp, bạn có thể kết hợp lapply.SDnhư sau

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

13
+1 Nhưng 0.296 so với 0.059 không đặc biệt ấn tượng. Kích thước dữ liệu cần lớn hơn 300k hàng và với hơn 3 nhóm, để data.table tỏa sáng. Chẳng hạn, chúng tôi sẽ thử và hỗ trợ hơn 2 tỷ hàng, vì một số người dùng data.table có 250GB RAM và GNU R hiện hỗ trợ độ dài> 2 ^ 31.
Matt Dowle

2
Thật. Hóa ra tôi không có tất cả RAM đó, và chỉ đơn giản là cố gắng cung cấp một số bằng chứng về hiệu suất vượt trội của data.table. Tôi chắc rằng sự khác biệt sẽ còn lớn hơn với nhiều dữ liệu hơn.
asieira

1
Tôi đã có 7 triệu lần quan sát dplyr mất .3 giây và tổng hợp () mất 22 giây để hoàn thành thao tác. Tôi sẽ đăng nó về chủ đề này và bạn đánh bại tôi với nó!
zemo

3
Có một cách thậm chí ngắn hơn để viết này data[, sum(Frequency), by = Category]. Bạn có thể sử dụng .Nmà thay thế sum()chức năng. data[, .N, by = Category]. Đây là một chiếc áo choàng
Stophface

3
Chỉ sử dụng .N sẽ tương đương với tổng (Tần số) nếu tất cả các giá trị trong cột Tần số bằng 1, vì .N đếm số lượng hàng trong mỗi bộ tổng hợp (.SD). Và đó không phải là trường hợp ở đây.
Asieira

41

Bạn cũng có thể sử dụng hàm by () :

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

Các gói khác (plyr, định hình lại) có lợi ích trả về data.frame, nhưng nó đáng để làm quen với () vì đây là hàm cơ bản.


28

Vài năm sau, chỉ cần thêm một giải pháp cơ sở R đơn giản khác không có ở đây vì một số lý do- xtabs

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

Hoặc nếu bạn muốn data.frametrở lại

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))

23

Nếu xlà một khung dữ liệu với dữ liệu của bạn, thì sau đây sẽ làm những gì bạn muốn:

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

19

Mặc dù gần đây tôi đã trở thành một người chuyển đổi dplyrcho hầu hết các loại hoạt động này,sqldf gói vẫn thực sự tốt (và IMHO dễ đọc hơn) cho một số thứ.

Dưới đây là một ví dụ về cách trả lời câu hỏi này 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

Chỉ cần thêm tùy chọn thứ ba:

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

EDIT: đây là một câu trả lời rất cũ. Bây giờ tôi muốn giới thiệu việc sử dụng group_bysummarisetừ dplyr, như trong câu trả lời @docendo.


7

Tôi tìm thấy ave rất hữu ích (và hiệu quả) khi bạn cần áp dụng các hàm tổng hợp khác nhau trên các cột khác nhau (và bạn phải / muốn gắn vào cơ sở R):

ví dụ

Cho đầu vào này:

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

chúng tôi muốn nhóm bằng Categ1Categ2và tính tổng của Samplesvà có ý nghĩa của Freq.
Đây là một giải pháp có thể sử dụng 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),]

Kết quả :

> 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

Việc thêm gần đây dplyr::tally()bây giờ làm cho điều này dễ dàng hơn bao giờ hết:

tally(x, Category)

Category     n
First        30
Second       5
Third        34

6

Bạn có thể sử dụng chức năng group.sumtừ gói 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 có nhiều chức năng nhóm vàgroup.sumlà một trong số đó.


4

sử dụng castthay vì recast(lưu ý 'Frequency'là ngay bây giờ '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)

để có được:

Category (all)
First     30
Second    5
Third     34

2

Một giải pháp khác trả về tổng của các nhóm trong ma trận hoặc khung dữ liệu và ngắn và nhanh:

rowsum(x$Frequency, x$Category)

Độc đáo, và thực sự nhanh chóng.
jay.sf

0

dplyr 1.0.0, across()chức năng có thể được sử dụng:

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

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

Nếu quan tâm đến nhiều biến:

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

Và việc lựa chọn các biến bằng cách sử dụng trợ giúp chọn:

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

Dữ liệu mẫu:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.