Làm thế nào để tóm tắt dữ liệu theo nhóm trong R? [đóng cửa]


181

Tôi có khung dữ liệu R như thế này:

        age group
1   23.0883     1
2   25.8344     1
3   29.4648     1
4   32.7858     2
5   33.6372     1
6   34.9350     1
7   35.2115     2
8   35.2115     2
9   35.2115     2
10  36.7803     1
...

Tôi cần lấy khung dữ liệu theo mẫu sau:

group mean     sd
1     34.5     5.6
2     32.3     4.2
...

Số nhóm có thể khác nhau, nhưng tên và số lượng của họ có thể nhận được bằng cách gọi levels(factor(data$group))

Những thao tác nào nên được thực hiện với dữ liệu để có kết quả?


dấu phẩy trong khung dữ liệu kết quả có nghĩa là một cái gì đó đặc biệt, hay nó chỉ là dấu thập phân?
mpiktas

@mpiktas Cảm ơn bạn đã lưu ý. Đã sửa. Đây là những vấn đề cục bộ (tôi là người Nga) - chúng tôi sử dụng dấu phẩy để phân tách thập phân.
Yuriy Petrovskiy

3
Tôi nghi ngờ điều đó. Tất cả châu Âu sử dụng dấu phẩy trừ người Anh.
mpiktas

4
Mặc dù không phải là người Anh, tôi thích dấu chấm cho dấu phân cách thập phân.
Roman Luštrik

1
Xem aggregate, tapplyvà sau đó stackoverflow.com cho bất kỳ câu hỏi mã hóa tiếp theo thuộc loại này.
liên hợp chiến

Câu trả lời:


140

Đây là plyr một dòng biến thể sử dụng ddply :

dt <- data.frame(age=rchisq(20,10),group=sample(1:2,20,rep=T))
ddply(dt,~group,summarise,mean=mean(age),sd=sd(age))

Dưới đây là một biến thể một dòng sử dụng gói mới data.table .

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)
dt[,list(mean=mean(age),sd=sd(age)),by=group]

Cái này nhanh hơn, mặc dù điều này chỉ đáng chú ý trên bảng với 100k hàng. Thời gian trên Macbook Pro của tôi với bộ xử lý 2.53 Ghz Core 2 Duo và R 2.11.1:

> system.time(aa <- ddply(dtf,~group,summarise,mean=mean(age),sd=sd(age)))
utilisateur     système      écoulé 
      0.513       0.180       0.692 
> system.time(aa <- dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.087       0.018       0.103 

Tiết kiệm hơn nữa là có thể nếu chúng ta sử dụng setkey:

> setkey(dt,group)
> system.time(dt[,list(mean=mean(age),sd=sd(age)),by=group])
utilisateur     système      écoulé 
      0.040       0.007       0.048 

2
@chl, nó đã cho tôi cơ hội dùng thử gói data.table mới này . Nó trông thực sự hứa hẹn.
mpiktas

7
+6000 cho dữ liệu. Nó thực sự nhanh hơn rất nhiều so với ddply, ngay cả đối với tôi trên các bộ dữ liệu nhỏ hơn 100k (tôi có một cái chỉ với 20k hàng). Phải là một cái gì đó để làm với các chức năng tôi đang áp dụng, nhưng ddply sẽ mất vài phút và data.table vài giây.
nguyên tử

Lỗi đánh máy đơn giản: Tôi nghĩ bạn có nghĩa là dt <- data.table(dtf)thay vì dt <- data.table(dt)trong khối mã thứ hai. Bằng cách đó, bạn đang tạo bảng dữ liệu từ khung dữ liệu thay vì từ dthàm từ statsgói. Tôi đã thử chỉnh sửa nó, nhưng tôi không thể chỉnh sửa dưới sáu ký tự.
Christopher Bottoms

Theo ý kiến ​​của tôi (không khiêm tốn trong trường hợp này) data.tablelà cách tốt nhất để tổng hợp dữ liệu và câu trả lời này là tuyệt vời, nhưng vẫn chỉ làm trầy xước bề mặt. Bên cạnh việc vượt trội về mặt cú pháp, nó cũng cực kỳ linh hoạt và có nhiều tính năng tiên tiến liên quan đến các phép nối và cơ học bên trong. Kiểm tra Câu hỏi thường gặp, trang github hoặc khóa học để biết thêm thông tin.
genorama

97

Một khả năng là sử dụng hàm tổng hợp . Ví dụ,

aggregate(data$age, by=list(data$group), FUN=mean)[2]

cung cấp cho bạn cột thứ hai của kết quả mong muốn.


1
Không liên kết với máy chủ trợ giúp tại địa phương của bạn :-) +1 nhưng hãy xem nhận xét của tôi về phản hồi của @ steffen.
chl

Xong việc bằng cách gọi data.frame(group=levels(factor(data$group)),mean=(aggregate(data$age, by=list(data$group), FUN=mean)$x),sd=(aggregate(data$age, by=list(data$group), FUN=sd)$x))nhưng tôi không chắc đó là cách chính xác. Tôi không chắc điều gì sẽ xảy ra sau đó kết quả của các cột được liên kết sẽ theo thứ tự khác nhau (tôi nghĩ điều đó là có thể). Ý kiến ​​của bạn là gì?
Yuriy Petrovskiy

9
@Yuriy Các hàng không nên bị lỗi, nhưng đây là một cách để thực hiện một cuộc gọi đến aggregate():aggregate(age ~ group, data=dat, FUN = function(x) c(M=mean(x), SD=sd(x)))
khóa

@lockedoff: Cảm ơn bạn đã hoàn thành câu trả lời của tôi!
ocram

27

Vì bạn đang thao tác một khung dữ liệu, nên dplyrgói có lẽ là cách nhanh hơn để làm điều đó.

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
grp <- group_by(dt, group)
summarise(grp, mean=mean(age), sd=sd(age))

hoặc tương đương, sử dụng toán tử dplyr/ magrittrpipe:

library(dplyr)
dt <- data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T))
group_by(dt, group) %>%
 summarise(mean=mean(age), sd=sd(age))

EDIT sử dụng đầy đủ của nhà điều hành đường ống:

library(dplyr)
data.frame(age=rchisq(20,10), group=sample(1:2,20, rep=T)) %>%
  group_by(group) %>%
  summarise(mean=mean(age), sd=sd(age))

3
+1 cho dplyr. Nó đã làm cho rất nhiều tác vụ R trở nên đơn giản và nhiều phương thức này đã lỗi thời.
gregmacfarlane

Thật không may, việc sử dụng đầy đủ phiên bản vận hành đường ống không hiệu quả với tôi
dagcilibili

bạn đã tải dplyr hoặc magrittr?
Bastiaan Quast

cảm ơn bạn rất nhiều @bquast đã chỉ ra giải pháp, chức năng tóm tắt được gọi từ plyrthay vì dplyrgây ra vấn đề.
dagcilibili

12

Tuyệt vời, cảm ơn bquast đã thêm giải pháp dplyr!

Hóa ra sau đó, dplyr và data.table rất gần nhau:

library(plyr)
library(dplyr)
library(data.table)
library(rbenchmark)

dtf <- data.frame(age=rchisq(100000,10),group=factor(sample(1:10,100000,rep=T)))
dt <- data.table(dtf)

setkey(dt,group)

a<-benchmark(ddply(dtf,~group,plyr:::summarise,mean=mean(age),sd=sd(age)),
         dt[,list(mean=mean(age),sd=sd(age)),by=group],
         group_by(dt, group) %>% summarise(mean=mean(age),sd=sd(age) ),
         group_by(dtf, group) %>% summarise(mean=mean(age),sd=sd(age) )
)

a[, c(1,3,4)]

data.table vẫn là nhanh nhất, được theo dõi rất chặt chẽ bởi dplyr (), điều thú vị có vẻ nhanh hơn trên data.frame so với data.table:

                                                              test elapsed relative
1 ddply(dtf, ~group, plyr:::summarise, mean = mean(age), sd = sd(age))   1.689    4.867
2               dt[, list(mean = mean(age), sd = sd(age)), by = group]   0.347    1.000
4   group_by(dtf, group) %>% summarise(mean = mean(age), sd = sd(age))   0.369    1.063
3    group_by(dt, group) %>% summarise(mean = mean(age), sd = sd(age))   0.580    1.671

Lúc đầu tôi nghĩ bạn cần chuyển setkey vào điểm chuẩn, nhưng hóa ra hầu như không mất thời gian.
kasterma

10

Ngoài các đề xuất hiện có, bạn có thể muốn kiểm tra describe.bychức năng trong psychgói.

Nó cung cấp một số thống kê mô tả bao gồm độ lệch trung bình và độ lệch chuẩn dựa trên một biến nhóm.


thật tuyệt, nhưng hơi khó để xuất sang LaTeX IME.
richiemorrisroe

10

Tôi đã tìm thấy chức năng summaryBytrong gói doBy để thuận tiện nhất cho việc này:

library(doBy)

age    = c(23.0883, 25.8344, 29.4648, 32.7858, 33.6372,
           34.935,  35.2115, 35.2115,  5.2115, 36.7803)
group  = c(1, 1, 1, 2, 1, 1, 2, 2, 2, 1)
dframe = data.frame(age=age, group=group)

summaryBy(age~group, data=dframe, FUN=c(mean, sd))
# 
#   group age.mean    age.sd
# 1     1 30.62333  5.415439
# 2     2 27.10507 14.640441

9

Sử dụng sqldfgói. Điều này cho phép bạn sử dụng SQL để tóm tắt dữ liệu. Khi bạn tải nó, bạn có thể viết một cái gì đó như -

sqldf('  select group,avg(age) from data group by group  ')

8

Đã chỉnh sửa: Theo đề xuất của chl

Hàm bạn đang tìm kiếm được gọi là "tapply", áp dụng một hàm cho mỗi nhóm được chỉ định bởi một yếu tố.

# create some artificial data
set.seed(42)
groups <- 5

agedat <- c()
groupdat <- c()

for(group in 1:groups){
    agedat <- c(agedat,rnorm(100,mean=0 + group,1/group))
    groupdat <- c(groupdat,rep(group,100))
}
dat <- data.frame("age"=agedat,"group"=factor(groupdat))

# calculate mean and stdev age per group
res <- rbind.data.frame(group=1:5, with(dat, tapply(age, group, function(x) c(mean(x), sd(x)))))
names(res) <- paste("group",1:5)
row.names(res)[2:3] <- c("mean","sd")

Tôi thực sự đề nghị làm việc thông qua một hướng dẫn R cơ bản giải thích tất cả các phương pháp và cơ sở dữ liệu thường được sử dụng. Nếu không, bạn sẽ bị kẹt mỗi inch trong khi lập trình. Xem câu hỏi này cho một bộ sưu tập các tài nguyên có sẵn miễn phí.


2
@steffen +1 nhưng không cần forvòng lặp ở đây, bạn có thể đối chiếu luồng dữ liệu nội tuyến của mình, IMO. Đối với tapplycuộc gọi, sử dụng function(x) c(mean(x),sd(x)))cbindkết quả như OP yêu cầu cho cả hai số liệu thống kê. Ngoài ra, ddplytừ gói plyr có thể làm điều này trơn tru.
chl

@steffen Vấn đề là tôi cần chính xác cấu trúc bảng tôi đã mô tả. Không có vấn đề với việc có được phương tiện và sd. Vấn đề là với cấu trúc.
Yuriy Petrovskiy

@chl: Cảm ơn bạn đã nhận xét của bạn, không biết về plyr :). Tôi đã thêm cbind, nhưng phần còn lại không bị ảnh hưởng. Có thể một người khác lấy tín dụng, câu trả lời này sẽ vẫn là một ví dụ ít tối ưu hơn.
steffen

@Yuriy: Đã thêm cbind. Nếu bạn đã biết cách áp dụng các chức năng cho mỗi nhóm, bạn có thể định dạng lại câu hỏi của mình (chỉ để rõ ràng;)).
steffen

@steffen cbind("mean"=mperage,"stdev"=stperage) gives no 'group' column. Will be joining by cbind (nhóm = cấp độ (yếu tố (dữ liệu $ nhóm)), "mean" = mperage, "stdev" = stperage) `đúng không?
Yuriy Petrovskiy

7

Đây là một ví dụ với chức năng aggregates()tôi đã tự làm cách đây một thời gian:

# simulates data
set.seed(666)
( dat <- data.frame(group=gl(3,6), level=factor(rep(c("A","B","C"), 6)), 
                    y=round(rnorm(18,10),1)) )

> dat
   group level    y
1      1     A 10.8
2      1     B 12.0
3      1     C  9.6
4      1     A 12.0
5      1     B  7.8
6      1     C 10.8
7      2     A  8.7
8      2     B  9.2
9      2     C  8.2
10     2     A 10.0
11     2     B 12.2
12     2     C  8.2
13     3     A 10.9
14     3     B  8.3
15     3     C 10.1
16     3     A  9.9
17     3     B 10.9
18     3     C 10.3

# aggregates() function
aggregates <- function(formula, data=NULL, FUNS){ 
    if(class(FUNS)=="list"){ 
        f <- function(x) sapply(FUNS, function(fun) fun(x)) 
    }else{f <- FUNS} 
    temp <- aggregate(formula, data, f) 
    out <- data.frame(temp[,-ncol(temp)], temp[,ncol(temp)]) 
    colnames(out)[1] <- colnames(temp)[1] 
return(out) 
} 

# example 
FUNS <- function(x) c(mean=round(mean(x),0), sd=round(sd(x), 0)) 
( ag <- aggregates(y~group:level, data=dat, FUNS=FUNS) ) 

Nó cho kết quả như sau:

> ag
  group level mean sd
1     1     A   11  1
2     2     A    9  1
3     3     A   10  1
4     1     B   10  3
5     2     B   11  2
6     3     B   10  2
7     1     C   10  1
8     2     C    8  0
9     3     C   10  0

Có lẽ bạn có thể nhận được kết quả tương tự bắt đầu từ hàm R split ():

> with(dat, sapply( split(y, group:level), FUNS ) )
     1:A 1:B 1:C 2:A 2:B 2:C 3:A 3:B 3:C
mean  11  10  10   9  11   8  10  10  10
sd     1   3   1   1   2   0   1   2   0

Hãy để tôi trở lại đầu ra của aggregateshàm. Bạn có thể biến nó trong một bảng đẹp sử dụng reshape(), xtabs()ftable():

rag <- reshape(ag, varying=list(3:4), direction="long", v.names="y") 
rag$time <- factor(rag$time) 
ft <- ftable(xtabs(y~group+level+time, data=rag)) 
attributes(ft)$col.vars <- list(c("mean","sd")) 

Điều này mang lại:

> ft 
             mean sd
group level         
1     A        11  1
      B        10  3
      C        10  1
2     A         9  1
      B        11  2
      C         8  0
3     A        10  1
      B        10  2
      C        10  0

Đẹp phải không? Bạn có thể xuất bảng này sang pdf với textplot()chức năng củagplots gói.

Xem ở đây để biết giải pháp của người khác.

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.