Các cách nhanh chóng trong R để có được hàng đầu tiên của khung dữ liệu được nhóm theo một mã định danh [đã đóng]


14

Đôi khi tôi chỉ cần lấy hàng đầu tiên của tập dữ liệu được nhóm theo một mã định danh, như khi truy xuất tuổi và giới tính khi có nhiều quan sát cho mỗi cá nhân. Cách nhanh nhất (hoặc nhanh nhất) để làm điều này trong R là gì? Tôi đã sử dụng tổng hợp () bên dưới và nghi ngờ có những cách tốt hơn. Trước khi đăng câu hỏi này, tôi đã tìm kiếm một chút trên google, tìm và thử ddply, và rất ngạc nhiên vì nó rất chậm và khiến tôi bị lỗi bộ nhớ trên tập dữ liệu của mình (400.000 hàng x 16 cols, 7.000 ID duy nhất), trong khi phiên bản tổng hợp () khá nhanh

(dx <- data.frame(ID = factor(c(1,1,2,2,3,3)), AGE = c(30,30,40,40,35,35), FEM = factor(c(1,1,0,0,1,1))))
# ID AGE FEM
#  1  30   1
#  1  30   1
#  2  40   0
#  2  40   0
#  3  35   1
#  3  35   1
ag <- data.frame(ID=levels(dx$ID))
ag <- merge(ag, aggregate(AGE ~ ID, data=dx, function(x) x[1]), "ID")
ag <- merge(ag, aggregate(FEM ~ ID, data=dx, function(x) x[1]), "ID")
ag
# ID AGE FEM
#  1  30   1
#  2  40   0
#  3  35   1
#same result:
library(plyr)
ddply(.data = dx, .var = c("ID"), .fun = function(x) x[1,])

CẬP NHẬT: Xem câu trả lời của Chase và bình luận của Matt Parker về những gì tôi cho là cách tiếp cận thanh lịch nhất. Xem câu trả lời của @Matthew Dowle để biết giải pháp nhanh nhất sử dụng data.tablegói.


Cảm ơn tất cả các câu trả lời của bạn. Giải pháp data.table của @Steve là nhanh nhất với hệ số ~ 5 trên tập dữ liệu của tôi so với giải pháp tổng hợp () của @Gavin (lần lượt nhanh hơn mã tổng hợp () của tôi) và hệ số ~ 7.5 qua giải pháp by () của @Matt. Tôi không có ý tưởng định hình lại vì tôi không thể làm cho nó hoạt động nhanh được. Tôi đoán giải pháp mà @Chase đưa ra sẽ là nhanh nhất và đó thực sự là điều tôi đang tìm kiếm, nhưng khi tôi bắt đầu viết bình luận này, mã không hoạt động (tôi thấy nó đã được sửa bây giờ!).
khóa

Trên thực tế @Chase đã nhanh hơn với hệ số ~ 9 so với data.table, vì vậy tôi đã thay đổi câu trả lời được chấp nhận của mình. Cảm ơn một lần nữa mọi người - đã học được một loạt các công cụ mới.
khóa

xin lỗi, tôi đã sửa mã của tôi Một lưu ý hay mẹo ở đây là ghép một giá trị không phải là một trong các ID của bạn diff()để bạn có thể lấy ID đầu tiên dx.
Đuổi theo

Câu trả lời:


10

Là cột ID của bạn thực sự là một yếu tố? Nếu nó thực sự là số, tôi nghĩ bạn có thể sử dụng diffhàm để lợi thế của bạn. Bạn cũng có thể ép nó thành số với as.numeric().

dx <- data.frame(
    ID = sort(sample(1:7000, 400000, TRUE))
    , AGE = sample(18:65, 400000, TRUE)
    , FEM = sample(0:1, 400000, TRUE)
)

dx[ diff(c(0,dx$ID)) != 0, ]

1
Tài giỏi! Bạn cũng có thể làm dx[c(TRUE, dx$ID[-1] != dx$ID[-length(dx$ID)], ]với dữ liệu không phải là số - tôi nhận được 0,03 cho ký tự, 0,05 cho các yếu tố. PS: có thêm một chức năng )đầu tiên của bạn system.time(), sau số không thứ hai.
Matt Parker

@Matt - Cuộc gọi tốt và bắt tốt. Hôm nay tôi dường như không thể sao chép / dán mã đáng giá.
Đuổi theo

Tôi đang làm việc với chương trình Thuê xe đạp Luân Đôn và cần tìm cách tìm ra những trường hợp đầu tiên và cuối cùng của việc thuê người đi xe đạp. Với 1 triệu người dùng, 10 triệu chuyến đi mỗi năm và dữ liệu vài năm, vòng lặp "cho" của tôi đã thực hiện 1 người dùng mỗi giây. Tôi đã thử giải pháp "bằng" và không hoàn thành sau một giờ. Lúc đầu, tôi không thể hiểu được "giải pháp thay thế cho giải pháp của Chase" đang làm gì, nhưng cuối cùng đồng xu đã giảm và nó sẽ thực thi trong vài giây. Vì vậy, quan điểm về sự cải thiện trở nên lớn hơn với các bộ dữ liệu lớn hơn được chứng minh bằng kinh nghiệm của tôi.
George Simpson

@GeorgeSimpson - rất vui khi thấy điều này vẫn đang được tham khảo! Các data.tablegiải pháp xuống dưới đây sẽ chứng minh là nhanh nhất, vì vậy tôi muốn kiểm tra xem ra nếu tôi là bạn (nó có lẽ sẽ là câu trả lời được chấp nhận ở đây).
Đuổi theo

17

Theo dõi câu trả lời của Steve, có một cách nhanh hơn nhiều trong data.table:

> # Preamble
> dx <- data.frame(
+     ID = sort(sample(1:7000, 400000, TRUE))
+     , AGE = sample(18:65, 400000, TRUE)
+     , FEM = sample(0:1, 400000, TRUE)
+ )
> dxt <- data.table(dx, key='ID')

> # fast self join
> system.time(ans2<-dxt[J(unique(ID)),mult="first"])
 user  system elapsed 
0.048   0.016   0.064

> # slower using .SD
> system.time(ans1<-dxt[, .SD[1], by=ID])
  user  system elapsed 
14.209   0.012  14.281 

> mapply(identical,ans1,ans2)  # ans1 is keyed but ans2 isn't, otherwise identical
  ID  AGE  FEM 
TRUE TRUE TRUE 

Nếu bạn chỉ cần hàng đầu tiên của mỗi nhóm, thì việc tham gia trực tiếp vào hàng đó sẽ nhanh hơn nhiều. Tại sao tạo đối tượng .SD mỗi lần, chỉ để sử dụng hàng đầu tiên của nó?

So sánh 0,064 dữ liệu. Có thể thay thế "Giải pháp thay thế của Matt Park cho giải pháp của Chase" (dường như là nhanh nhất cho đến nay):

> system.time(ans3<-dxt[c(TRUE, dxt$ID[-1] != dxt$ID[-length(dxt$ID)]), ])
 user  system elapsed 
0.284   0.028   0.310 
> identical(ans1,ans3)
[1] TRUE 

Nhanh hơn gấp 5 lần, nhưng đó là một cái bàn nhỏ dưới 1 triệu hàng. Khi kích thước tăng lên, sự khác biệt cũng vậy.


Ồ, tôi chưa bao giờ thực sự đánh giá cao [.data.tablechức năng "thông minh" có thể nhận được như thế nào ... Tôi đoán tôi đã không nhận ra bạn đã không tạo ra một .SDđối tượng nếu bạn không thực sự cần nó. Đẹp quá
Steve Lianoglou

Vâng, đó là thực sự nhanh chóng! Ngay cả khi bạn đưa dxt <- data.table(dx, key='ID')vào lệnh gọi system.time (), nó vẫn nhanh hơn giải pháp của @ Matt.
khóa

Tôi đoán điều này đã lỗi thời vì các phiên bản data.table mới hơn SD[1L]đã được tối ưu hóa hoàn toàn và thực sự câu trả lời @SteveLianoglou sẽ nhanh gấp đôi cho 5e7 hàng.
David Arenburg

@DavidArenburg Kể từ v1.9.8 tháng 11 năm 2016, vâng. Vui lòng chỉnh sửa câu trả lời này trực tiếp, hoặc có thể Q này cần phải là wiki cộng đồng hoặc một cái gì đó.
Matt Dowle

10

Bạn không cần nhiều merge()bước, chỉ cần aggregate()cả hai biến quan tâm:

> aggregate(dx[, -1], by = list(ID = dx$ID), head, 1)
  ID AGE FEM
1  1  30   1
2  2  40   0
3  3  35   1

> system.time(replicate(1000, aggregate(dx[, -1], by = list(ID = dx$ID), 
+                                       head, 1)))
   user  system elapsed 
  2.531   0.007   2.547 
> system.time(replicate(1000, {ag <- data.frame(ID=levels(dx$ID))
+ ag <- merge(ag, aggregate(AGE ~ ID, data=dx, function(x) x[1]), "ID")
+ ag <- merge(ag, aggregate(FEM ~ ID, data=dx, function(x) x[1]), "ID")
+ }))
   user  system elapsed 
  9.264   0.009   9.301

Thời gian so sánh:

1) Giải pháp của Matt:

> system.time(replicate(1000, {
+ agg <- by(dx, dx$ID, FUN = function(x) x[1, ])
+ # Which returns a list that you can then convert into a data.frame thusly:
+ do.call(rbind, agg)
+ }))
   user  system elapsed 
  3.759   0.007   3.785

2) Giải pháp định hình lại của Zach2:

> system.time(replicate(1000, {
+ dx <- melt(dx,id=c('ID','FEM'))
+ dcast(dx,ID+FEM~variable,fun.aggregate=mean)
+ }))
   user  system elapsed 
 12.804   0.032  13.019

3) Giải pháp dữ liệu của Steve.

> system.time(replicate(1000, {
+ dxt <- data.table(dx, key='ID')
+ dxt[, .SD[1,], by=ID]
+ }))
   user  system elapsed 
  5.484   0.020   5.608 
> dxt <- data.table(dx, key='ID') ## one time step
> system.time(replicate(1000, {
+ dxt[, .SD[1,], by=ID] ## try this one line on own
+ }))
   user  system elapsed 
  3.743   0.006   3.784

4) Truy tìm giải pháp nhanh bằng cách sử dụng số, không phải yếu tố , ID:

> dx2 <- within(dx, ID <- as.numeric(ID))
> system.time(replicate(1000, {
+ dy <- dx[order(dx$ID),]
+ dy[ diff(c(0,dy$ID)) != 0, ]
+ }))
   user  system elapsed 
  0.663   0.000   0.663

và 5) Giải pháp thay thế của Matt Park cho giải pháp của Chase, về tính cách hoặc yếu tố ID, nhanh hơn một chút so với giải pháp số của Chase ID:

> system.time(replicate(1000, {
+ dx[c(TRUE, dx$ID[-1] != dx$ID[-length(dx$ID)]), ]
+ }))
   user  system elapsed 
  0.513   0.000   0.516

Ồ, đúng rồi, cảm ơn! Quên về cú pháp đó cho tổng hợp.
khóa

Nếu bạn muốn thêm giải pháp của Chase, đây là những gì tôi nhận được:dx$ID <- sample(as.numeric(dx$ID)) #assuming IDs arent presorted system.time(replicate(1000, { dy <- dx[order(dx$ID),] dy[ diff(c(0,dy$ID)) != 0, ] })) user system elapsed 0.58 0.00 0.58
khóa

@lockedoff - xong, cảm ơn, nhưng tôi không lấy mẫu ngẫu nhiên IDđể kết quả có thể so sánh với các giải pháp khác.
Phục hồi Monica - G. Simpson

Và thời gian phiên bản @Matt Parker trong các bình luận cho câu trả lời của @ Chase
Tái lập lại Monica - G. Simpson

2
Cảm ơn vì đã tính thời gian, Gavin - điều đó thực sự hữu ích cho những câu hỏi như thế này.
Matt Parker

9

Bạn có thể thử sử dụng gói data.table .

Đối với trường hợp cụ thể của bạn, nhược điểm là nó (cực kỳ nhanh). Lần đầu tiên tôi được giới thiệu về nó, tôi đã làm việc trên các đối tượng data.frame với hàng trăm ngàn hàng. "Bình thường" aggregatehoặc ddplycác phương pháp đã được thực hiện ~ 1-2 phút để hoàn thành (điều này là trước khi Hadley giới thiệu idata.framemojo vào ddply). Sử dụng data.table, các hoạt động đã được thực hiện trong vài giây.

Nhược điểm của nó là quá nhanh bởi vì nó sẽ sử dụng dữ liệu của bạn. Nó giống như data.frame) bằng "các cột chính" và sử dụng chiến lược tìm kiếm thông minh để tìm các tập hợp con của dữ liệu của bạn. Điều này sẽ dẫn đến việc sắp xếp lại dữ liệu của bạn trước khi bạn thu thập số liệu thống kê về dữ liệu đó.

Cho rằng bạn sẽ chỉ muốn hàng đầu tiên của mỗi nhóm - có thể việc sắp xếp lại sẽ làm rối tung hàng nào trước, đó là lý do tại sao nó có thể không phù hợp trong tình huống của bạn.

Dù sao, bạn sẽ phải đánh giá xem có data.tablephù hợp ở đây hay không , nhưng đây là cách bạn sẽ sử dụng nó với dữ liệu bạn đã trình bày:

install.packages('data.table') ## if yo udon't have it already
library(data.table)
dxt <- data.table(dx, key='ID')
dxt[, .SD[1,], by=ID]
     ID AGE FEM
[1,]  1  30   1
[2,]  2  40   0
[3,]  3  35   1

Cập nhật: Matthew Dowle (nhà phát triển chính của gói data.table) đã cung cấp một cách tốt hơn / thông minh hơn / cực kỳ hiệu quả hơn để sử dụng data.table để giải quyết vấn đề này như một trong những câu trả lời ở đây ... chắc chắn kiểm tra xem .


4

Hãy thử định hình lại2

library(reshape2)
dx <- melt(dx,id=c('ID','FEM'))
dcast(dx,ID+FEM~variable,fun.aggregate=mean)

3

Bạn có thể thử

agg <- by(dx, dx$ID, FUN = function(x) x[1, ])
# Which returns a list that you can then convert into a data.frame thusly:
do.call(rbind, agg)

Tôi không biết nếu điều này sẽ nhanh hơn plyr, mặc dù.

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.