Các hàm nhóm (tapply, by, tổng hợp) và nhóm * áp dụng


1040

Bất cứ khi nào tôi muốn làm một cái gì đó "ánh xạ" py trong R, tôi thường cố gắng sử dụng một chức năng trong apply gia đình.

Tuy nhiên, tôi chưa bao giờ hiểu được sự khác biệt giữa chúng - làm thế nào { sapply,lapply v.v.} áp dụng chức năng cho đầu vào / đầu vào được nhóm, đầu ra sẽ như thế nào, hoặc thậm chí đầu vào có thể là gì - vì vậy tôi thường chỉ cần đi qua tất cả cho đến khi tôi có được những gì tôi muốn.

Ai đó có thể giải thích làm thế nào để sử dụng cái nào khi?

Sự hiểu biết hiện tại (có thể không chính xác / không đầy đủ) của tôi là ...

  1. sapply(vec, f): đầu vào là một vectơ. đầu ra là một vectơ / ma trận, trong đó phần tử if(vec[i]), cung cấp cho bạn một ma trận nếu fcó đầu ra đa phần tử

  2. lapply(vec, f): giống như sapply, nhưng đầu ra là một danh sách?

  3. apply(matrix, 1/2, f): đầu vào là một ma trận. đầu ra là một vectơ, trong đó phần tử ilà f (hàng / col i của ma trận)
  4. tapply(vector, grouping, f): output là một ma trận / mảng, trong đó một phần tử trong ma trận / mảng là giá trị của fmột nhóm gvectơ và gđược đẩy đến tên hàng / col
  5. by(dataframe, grouping, f): hãy glà một nhóm. áp dụng fcho từng cột của nhóm / khung dữ liệu. đẹp in nhóm và giá trị của fmỗi cột.
  6. aggregate(matrix, grouping, f): tương tự by, nhưng thay vì in đẹp đầu ra, tổng hợp dán mọi thứ vào một khung dữ liệu.

Câu hỏi bên lề: Tôi vẫn chưa học plyr hoặc định hình lại - sẽ plyrhoặc reshapethay thế hoàn toàn những thứ này?


33
cho câu hỏi phụ của bạn: đối với nhiều thứ, plyr là sự thay thế trực tiếp cho *apply()by. plyr (ít nhất là với tôi) có vẻ phù hợp hơn nhiều ở chỗ tôi luôn biết chính xác định dạng dữ liệu mà nó mong đợi và chính xác những gì nó sẽ nhổ ra. Điều đó giúp tôi tiết kiệm rất nhiều rắc rối.
JD Long

12
Ngoài ra, tôi khuyên bạn nên thêm: doByvà khả năng lựa chọn và áp dụng data.table.
Lặp lại

7
sapplychỉ lapplyvới việc bổ sung simplify2arraytrên đầu ra. applykhông ép buộc vào vector nguyên tử, nhưng đầu ra có thể là vector hoặc danh sách. bychia các tệp dữ liệu thành các tệp dữ liệu con, nhưng nó không sử dụng friêng trên các cột. Chỉ khi có một phương thức cho 'data.frame'-class mới có thể fđược áp dụng theo cột by. aggregatelà chung nên các phương thức khác nhau tồn tại cho các lớp khác nhau của đối số đầu tiên.
IRTFM

8
Mnemonic: l dành cho 'list', s dành cho 'đơn giản hóa', t dành cho 'mỗi loại' (mỗi cấp độ của nhóm là một loại)
Lutz Prechelt

Ngoài ra còn tồn tại một số chức năng trong gói Rfast, như: Eachcol.apply, application.condition và hơn thế nữa, nhanh hơn các tương đương của R
Stefanos

Câu trả lời:


1330

R có nhiều hàm * áp dụng được mô tả ably trong các tệp trợ giúp (ví dụ ?apply). Tuy nhiên, có đủ trong số họ, việc bắt đầu sử dụng có thể gặp khó khăn trong việc quyết định xem cái nào phù hợp với tình huống của họ hoặc thậm chí ghi nhớ tất cả. Họ có thể có ý thức chung rằng "Tôi nên sử dụng chức năng * áp dụng ở đây", nhưng ban đầu có thể khó khăn để giữ tất cả chúng thẳng.

Mặc dù thực tế (lưu ý trong các câu trả lời khác) rằng phần lớn chức năng của gia đình * áp dụng được bao phủ bởi cực kỳ phổ biến plyr gói , các hàm cơ sở vẫn hữu ích và đáng để biết.

Câu trả lời này nhằm hoạt động như một loại biển chỉ dẫn cho các useR mới để giúp hướng họ đến hàm * áp dụng chính xác cho vấn đề cụ thể của họ. Lưu ý, điều này không nhằm mục đích đơn giản là lấy lại hoặc thay thế tài liệu R! Hy vọng là câu trả lời này giúp bạn quyết định * áp dụng chức năng nào phù hợp với tình huống của bạn và sau đó tùy thuộc vào bạn để nghiên cứu thêm. Với một ngoại lệ, sự khác biệt hiệu suất sẽ không được giải quyết.

  • áp dụng - Khi bạn muốn áp dụng một hàm cho các hàng hoặc cột của ma trận (và các tương tự chiều cao hơn); nói chung không nên cho các khung dữ liệu vì nó sẽ ép buộc vào một ma trận trước.

    # Two dimensional matrix
    M <- matrix(seq(1,16), 4, 4)
    
    # apply min to rows
    apply(M, 1, min)
    [1] 1 2 3 4
    
    # apply max to columns
    apply(M, 2, max)
    [1]  4  8 12 16
    
    # 3 dimensional array
    M <- array( seq(32), dim = c(4,4,2))
    
    # Apply sum across each M[*, , ] - i.e Sum across 2nd and 3rd dimension
    apply(M, 1, sum)
    # Result is one-dimensional
    [1] 120 128 136 144
    
    # Apply sum across each M[*, *, ] - i.e Sum across 3rd dimension
    apply(M, c(1,2), sum)
    # Result is two-dimensional
         [,1] [,2] [,3] [,4]
    [1,]   18   26   34   42
    [2,]   20   28   36   44
    [3,]   22   30   38   46
    [4,]   24   32   40   48

    Nếu bạn muốn phương tiện hàng / cột hoặc các khoản tiền cho một ma trận 2D, hãy chắc chắn để điều tra tối ưu hóa cao, nhanh như chớp colMeans, rowMeans, colSums, rowSums.

  • lapply - Khi bạn muốn áp dụng một chức năng cho từng thành phần của danh sách và lấy lại danh sách.

    Đây là đặc điểm của nhiều hàm * áp dụng khác. Bóc lại mã của họ và bạn sẽ thường tìm thấy lapplybên dưới.

    x <- list(a = 1, b = 1:3, c = 10:100) 
    lapply(x, FUN = length) 
    $a 
    [1] 1
    $b 
    [1] 3
    $c 
    [1] 91
    lapply(x, FUN = sum) 
    $a 
    [1] 1
    $b 
    [1] 6
    $c 
    [1] 5005
  • sapply - Khi bạn muốn áp dụng một hàm cho từng phần tử của danh sách, nhưng bạn muốn có một vectơ trở lại, thay vì một danh sách.

    Nếu bạn thấy mình gõ unlist(lapply(...)), hãy dừng lại và xem xét sapply.

    x <- list(a = 1, b = 1:3, c = 10:100)
    # Compare with above; a named vector, not a list 
    sapply(x, FUN = length)  
    a  b  c   
    1  3 91
    
    sapply(x, FUN = sum)   
    a    b    c    
    1    6 5005 

    Trong các ứng dụng nâng cao hơn của sapplynó sẽ cố gắng ép kết quả thành một mảng đa chiều, nếu phù hợp. Ví dụ: nếu hàm của chúng ta trả về các vectơ có cùng độ dài, sapplysẽ sử dụng chúng làm cột của ma trận:

    sapply(1:5,function(x) rnorm(3,x))

    Nếu hàm của chúng ta trả về một ma trận 2 chiều, sapplyvề cơ bản sẽ thực hiện cùng một việc, coi mỗi ma trận được trả về là một vectơ dài duy nhất:

    sapply(1:5,function(x) matrix(x,2,2))

    Trừ khi chúng tôi chỉ định simplify = "array", trong trường hợp đó, nó sẽ sử dụng các ma trận riêng lẻ để xây dựng một mảng đa chiều:

    sapply(1:5,function(x) matrix(x,2,2), simplify = "array")

    Tất cả các hành vi này tất nhiên phụ thuộc vào hàm của chúng ta trả về các vectơ hoặc ma trận có cùng độ dài hoặc kích thước.

  • vapply - Khi bạn muốn sử dụng sapplynhưng có lẽ cần phải tăng thêm một số tốc độ ra khỏi mã của bạn.

    Về vapplycơ bản, bạn cung cấp cho R một ví dụ về loại chức năng nào mà hàm của bạn sẽ trả về, điều này có thể tiết kiệm thời gian buộc các giá trị được trả về để khớp với một vectơ nguyên tử duy nhất.

    x <- list(a = 1, b = 1:3, c = 10:100)
    #Note that since the advantage here is mainly speed, this
    # example is only for illustration. We're telling R that
    # everything returned by length() should be an integer of 
    # length 1. 
    vapply(x, FUN = length, FUN.VALUE = 0L) 
    a  b  c  
    1  3 91
  • mapply - Vì khi bạn có một số cấu trúc dữ liệu (ví dụ: vectơ, danh sách) và bạn muốn áp dụng một hàm cho các phần tử thứ nhất của mỗi phần tử, và sau đó các phần tử thứ 2 của mỗi phần, v.v., ép kết quả vào một vectơ / mảng như trong sapply.

    Điều này là đa biến theo nghĩa là hàm của bạn phải chấp nhận nhiều đối số.

    #Sums the 1st elements, the 2nd elements, etc. 
    mapply(sum, 1:5, 1:5, 1:5) 
    [1]  3  6  9 12 15
    #To do rep(1,4), rep(2,3), etc.
    mapply(rep, 1:4, 4:1)   
    [[1]]
    [1] 1 1 1 1
    
    [[2]]
    [1] 2 2 2
    
    [[3]]
    [1] 3 3
    
    [[4]]
    [1] 4
  • Bản đồ - Một trình bao bọc mapplyvới SIMPLIFY = FALSE, vì vậy nó được đảm bảo trả về một danh sách.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - Cho khi bạn muốn áp dụng một hàm cho từng thành phần của cấu trúc danh sách lồng nhau , đệ quy.

    Để cho bạn một số ý tưởng về mức độ không phổ biến rapply, tôi đã quên nó khi lần đầu tiên đăng câu trả lời này! Rõ ràng, tôi chắc chắn nhiều người sử dụng nó, nhưng YMMV. rapplyđược minh họa tốt nhất với chức năng do người dùng xác định để áp dụng:

    # Append ! to string, otherwise increment
    myFun <- function(x){
        if(is.character(x)){
          return(paste(x,"!",sep=""))
        }
        else{
          return(x + 1)
        }
    }
    
    #A nested list structure
    l <- list(a = list(a1 = "Boo", b1 = 2, c1 = "Eeek"), 
              b = 3, c = "Yikes", 
              d = list(a2 = 1, b2 = list(a3 = "Hey", b3 = 5)))
    
    
    # Result is named vector, coerced to character          
    rapply(l, myFun)
    
    # Result is a nested list like l, with values altered
    rapply(l, myFun, how="replace")
  • tapply - Vì khi bạn muốn áp dụng một hàm cho các tập hợp con của một vectơ và các tập con được xác định bởi một số vectơ khác, thường là một yếu tố.

    Những con cừu đen của gia đình * áp dụng, thuộc loại. Việc sử dụng tập tin trợ giúp của cụm từ "mảng rách rưới" có thể hơi khó hiểu , nhưng thực ra nó khá đơn giản.

    Một vectơ:

    x <- 1:20

    Một yếu tố (có cùng độ dài!) Xác định các nhóm:

    y <- factor(rep(letters[1:5], each = 4))

    Thêm các giá trị xtrong mỗi nhóm con được xác định bởi y:

    tapply(x, y, sum)  
     a  b  c  d  e  
    10 26 42 58 74 

    Các ví dụ phức tạp hơn có thể được xử lý trong đó các nhóm con được xác định bởi các kết hợp duy nhất của danh sách một số yếu tố. tapplylà tinh thần tương tự với split-xin-kết hợp chức năng mà rất phổ biến trong R ( aggregate, by, ave, ddply, vv) Do đó tình trạng cừu đen của nó.


32
Hãy tin rằng bạn sẽ thấy đó bylà sự chia rẽ thuần túy và aggregatenằm tapplyở lõi của họ. Tôi nghĩ rằng cừu đen làm cho vải tuyệt vời.
IRTFM

21
Phản ứng tuyệt vời! Đây phải là một phần của tài liệu R chính thức :). Một gợi ý nhỏ: có lẽ thêm một số viên đạn vào sử dụng aggregatebylà tốt? (Cuối cùng tôi cũng hiểu chúng theo mô tả của bạn!, Nhưng chúng khá phổ biến, vì vậy có thể hữu ích khi tách ra và có một số ví dụ cụ thể cho hai chức năng đó.)
grautur

3
@grautur Tôi đã chủ động cắt tỉa những thứ từ câu trả lời này để tránh nó bị (a) quá dài và (b) viết lại tài liệu. Tôi đã quyết định rằng trong khi aggregate, byv.v. dựa trên các hàm * áp dụng, cách bạn tiếp cận sử dụng chúng đủ khác với quan điểm người dùng mà họ nên được tóm tắt trong một câu trả lời riêng biệt. Tôi có thể cố gắng rằng nếu tôi có thời gian, hoặc có thể người khác sẽ đánh bại tôi và kiếm được tiền.
Joran

4
đồng thời, ?Mapvới tư cách là họ hàng củamapply
baptiste

3
@jsanders - Tôi hoàn toàn không đồng ý với điều đó. data.frames là một phần hoàn toàn trung tâm của R và như một listđối tượng thường xuyên bị thao túng bằng cách sử dụng lapplyđặc biệt. Chúng cũng hoạt động như các thùng chứa để nhóm các vectơ / yếu tố của nhiều loại với nhau trong một bộ dữ liệu hình chữ nhật truyền thống. Mặc dù data.tableplyrcó thể thêm một loại cú pháp nhất định mà một số người có thể thấy thoải mái hơn, nhưng chúng đang mở rộng và hành động data.frametương ứng.
thelHRail

191

Bên cạnh đó, đây là cách các plyrhàm khác nhau tương ứng với các *applyhàm cơ sở (từ phần giới thiệu đến tài liệu plyr từ trang web plyr http://had.co.nz/plyr/ )

Base function   Input   Output   plyr function 
---------------------------------------
aggregate        d       d       ddply + colwise 
apply            a       a/l     aaply / alply 
by               d       l       dlply 
lapply           l       l       llply  
mapply           a       a/l     maply / mlply 
replicate        r       a/l     raply / rlply 
sapply           l       a       laply 

Một trong những mục tiêu của plyrlà cung cấp các quy ước đặt tên nhất quán cho từng hàm, mã hóa các kiểu dữ liệu đầu vào và đầu ra trong tên hàm. Nó cũng cung cấp tính nhất quán trong đầu ra, trong đó đầu ra từ dlply()có thể dễ dàng vượt qua ldply()để tạo ra đầu ra hữu ích, v.v.

Về mặt khái niệm, học tập plyrkhông khó hơn việc hiểu các *applychức năng cơ bản .

plyrvà các reshapechức năng đã thay thế gần như tất cả các chức năng này trong sử dụng hàng ngày của tôi. Nhưng, cũng từ tài liệu Giới thiệu đến Plyr:

Các chức năng liên quan tapplysweepkhông có chức năng tương ứng plyr, và vẫn hữu ích. mergelà hữu ích để kết hợp tóm tắt với dữ liệu gốc.


13
Khi tôi bắt đầu học R từ đầu, tôi thấy plyr NHIỀU dễ học hơn so với *apply()họ các hàm. Đối với tôi, ddply()rất trực quan vì tôi đã quen với các hàm tổng hợp SQL. ddply()trở thành cái búa của tôi để giải quyết nhiều vấn đề, một số trong đó có thể được giải quyết tốt hơn bằng các lệnh khác.
JD Long

1
Tôi đoán tôi đã hình dung rằng khái niệm đằng sau các plyrhàm tương tự như các *applyhàm, vì vậy nếu bạn có thể làm cái này, bạn có thể làm cái kia, nhưng các plyrhàm dễ nhớ hơn. Nhưng tôi hoàn toàn đồng ý về ddply()cái búa!
JoFrhwld

1
Gói plyr có join()chức năng thực hiện các nhiệm vụ tương tự như hợp nhất. Có lẽ nhiều hơn đến mức phải đề cập đến nó trong bối cảnh của plyr.
marbel

Chúng ta đừng quên nghèo, bị lãng quêneapply
JDL

Câu trả lời tuyệt vời nói chung, nhưng tôi nghĩ rằng nó hạ thấp các tiện ích vapplyvà nhược điểm của sapply. Một lợi thế lớn của vapplynó là nó thực thi loại đầu ra và độ dài, do đó bạn sẽ kết thúc với đầu ra dự kiến ​​chính xác hoặc một lỗi thông tin. Mặt khác, sapplysẽ cố gắng đơn giản hóa đầu ra theo các quy tắc không phải lúc nào cũng rõ ràng và quay lại danh sách khác. Chẳng hạn, hãy thử dự đoán loại đầu ra mà nó sẽ tạo ra : sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Thế còn sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov

133

Từ slide 21 của http://www.sl slideshoware.net/hadley/plyr-one-data-analytic-strargety :

áp dụng, sapply, lapply, bởi, tổng hợp

(Hy vọng rằng rõ ràng là applytương ứng với @ Hadley aaplyaggregatetương ứng với @ Hadley, ddplyv.v ... Slide 20 của cùng một trình chiếu sẽ làm rõ nếu bạn không lấy nó từ hình ảnh này.)

(bên trái là đầu vào, trên cùng là đầu ra)


4
Có một lỗi đánh máy trong slide? Ô trên cùng bên trái phải nhanh chóng
JHowIX

100

Đầu tiên hãy bắt đầu với câu trả lời tuyệt vời của Joran - nghi ngờ bất cứ điều gì có thể tốt hơn thế.

Sau đó, những điều ghi nhớ sau đây có thể giúp ghi nhớ sự khác biệt giữa mỗi lần. Trong khi một số là rõ ràng, những người khác có thể ít hơn --- vì những điều này bạn sẽ tìm thấy sự biện minh trong các cuộc thảo luận của Joran.

Ma thuật

  • lapplylà một danh sách áp dụng mà hoạt động trên một danh sách hoặc vector và trả về một danh sách.
  • sapplylà một đơn giản lapply (hàm mặc định để trả về một vectơ hoặc ma trận khi có thể)
  • vapplylà một áp dụng được xác minh (cho phép loại đối tượng trả về được chỉ định trước)
  • rapplylà một đệ quy áp dụng cho các danh sách lồng nhau, tức là danh sách trong danh sách
  • tapplylà một thẻ được áp dụng trong đó các thẻ xác định các tập hợp con
  • applychung : áp dụng một hàm cho các hàng hoặc cột của ma trận (hoặc, nói chung hơn, cho các kích thước của một mảng)

Xây dựng nền tảng đúng

Nếu sử dụng apply gia đình vẫn cảm thấy hơi xa lạ với bạn, thì có thể bạn đang thiếu một quan điểm chính.

Hai bài viết này có thể giúp đỡ. Họ cung cấp nền tảng cần thiết để thúc đẩy các kỹ thuật lập trình chức năng đang được cung cấp bởiapply gia đình các chức năng.

Người dùng Lisp sẽ nhận ra mô hình ngay lập tức. Nếu bạn không quen thuộc với Lisp, một khi bạn tìm hiểu về FP, bạn sẽ có được một quan điểm mạnh mẽ để sử dụng trong R - và applysẽ có ý nghĩa hơn rất nhiều.


51

Vì tôi nhận ra rằng (những câu trả lời rất xuất sắc) của bài đăng này thiếu byaggregategiải thích. Đây là đóng góp của tôi.

BỞI

Các bychức năng, như đã nêu trong tài liệu có thể được mặc dù, như là một "wrapper" cho tapply. Sức mạnh byphát sinh khi chúng ta muốn tính toán một nhiệm vụ tapplykhông thể xử lý. Một ví dụ là mã này:

ct <- tapply(iris$Sepal.Width , iris$Species , summary )
cb <- by(iris$Sepal.Width , iris$Species , summary )

 cb
iris$Species: setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 
-------------------------------------------------------------- 
iris$Species: versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 
-------------------------------------------------------------- 
iris$Species: virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 


ct
$setosa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.300   3.200   3.400   3.428   3.675   4.400 

$versicolor
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.525   2.800   2.770   3.000   3.400 

$virginica
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.200   2.800   3.000   2.974   3.175   3.800 

Nếu chúng ta in hai đối tượng này, ctcb, "về cơ bản" chúng ta có cùng kết quả và sự khác biệt duy nhất là cách chúng được hiển thị và các classthuộc tính khác nhau , tương ứng bycho cbarraychoct .

Như tôi đã nói, sức mạnh byphát sinh khi chúng ta không thể sử dụng tapply; đoạn mã sau đây là một ví dụ:

 tapply(iris, iris$Species, summary )
Error in tapply(iris, iris$Species, summary) : 
  arguments must have same length

R nói rằng các đối số phải có cùng độ dài, nói rằng "chúng tôi muốn tính toán summarytất cả các biến iristheo yếu tố Species": nhưng R không thể làm điều đó vì không biết cách xử lý.

Với byhàm R gửi một phương thức cụ thể cho data framelớp và sau đó cho phép summaryhàm hoạt động ngay cả khi độ dài của đối số đầu tiên (và kiểu cũng vậy) khác nhau.

bywork <- by(iris, iris$Species, summary )

bywork
iris$Species: setosa
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200   versicolor: 0  
 Median :5.000   Median :3.400   Median :1.500   Median :0.200   virginica : 0  
 Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246                  
 3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300                  
 Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600                  
-------------------------------------------------------------- 
iris$Species: versicolor
  Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
 Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
 1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
 Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
 Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
 3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
 Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
-------------------------------------------------------------- 
iris$Species: virginica
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400   setosa    : 0  
 1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800   versicolor: 0  
 Median :6.500   Median :3.000   Median :5.550   Median :2.000   virginica :50  
 Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026                  
 3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300                  
 Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500     

Nó thực sự hoạt động và kết quả là rất đáng ngạc nhiên. Nó là một đối tượng của lớp bymà theo đó Species(giả sử cho mỗi trong số chúng) tính toán summarycủa từng biến.

Lưu ý rằng nếu đối số đầu tiên là a data frame, hàm được gửi phải có một phương thức cho lớp đối tượng đó. Ví dụ: chúng tôi sử dụng mã này với meanchức năng, chúng tôi sẽ có mã này hoàn toàn không có ý nghĩa:

 by(iris, iris$Species, mean)
iris$Species: setosa
[1] NA
------------------------------------------- 
iris$Species: versicolor
[1] NA
------------------------------------------- 
iris$Species: virginica
[1] NA
Warning messages:
1: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
2: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA
3: In mean.default(data[x, , drop = FALSE], ...) :
  argument is not numeric or logical: returning NA

ĐỒNG Ý

aggregatecó thể được coi là một cách sử dụng khác tapplynếu chúng ta sử dụng nó theo cách như vậy.

at <- tapply(iris$Sepal.Length , iris$Species , mean)
ag <- aggregate(iris$Sepal.Length , list(iris$Species), mean)

 at
    setosa versicolor  virginica 
     5.006      5.936      6.588 
 ag
     Group.1     x
1     setosa 5.006
2 versicolor 5.936
3  virginica 6.588

Hai điểm khác biệt trước mắt là đối số thứ hai aggregate phải là một danh sách trong khi tapply có thể (không bắt buộc) là một danh sách và đầu ra của aggregatelà một khung dữ liệu trong khi đối số tapplylà mộtarray .

Sức mạnh của aggregatenó là nó có thể xử lý các tập hợp con dữ liệu dễ dàng bằng subsetđối số và nó cũng có các phương thức cho tscác đối tượng formula.

Những yếu tố này làm cho aggregatedễ dàng hơn để làm việc với điều đó tapplytrong một số tình huống. Dưới đây là một số ví dụ (có sẵn trong tài liệu):

ag <- aggregate(len ~ ., data = ToothGrowth, mean)

 ag
  supp dose   len
1   OJ  0.5 13.23
2   VC  0.5  7.98
3   OJ  1.0 22.70
4   VC  1.0 16.77
5   OJ  2.0 26.06
6   VC  2.0 26.14

Chúng ta có thể đạt được điều tương tự với tapplynhưng cú pháp hơi khó hơn và đầu ra (trong một số trường hợp) ít đọc hơn:

att <- tapply(ToothGrowth$len, list(ToothGrowth$dose, ToothGrowth$supp), mean)

 att
       OJ    VC
0.5 13.23  7.98
1   22.70 16.77
2   26.06 26.14

Có những lúc khác chúng ta không thể sử dụng byhoặc tapplychúng ta phải sử dụng aggregate.

 ag1 <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, mean)

 ag1
  Month    Ozone     Temp
1     5 23.61538 66.73077
2     6 29.44444 78.22222
3     7 59.11538 83.88462
4     8 59.96154 83.96154
5     9 31.44828 76.89655

Chúng ta không thể có được kết quả trước đó tapplytrong một cuộc gọi nhưng chúng ta phải tính giá trị trung bình Monthcho từng phần tử và sau đó kết hợp chúng (cũng lưu ý rằng chúng ta phải gọi na.rm = TRUE, bởi vì các formulaphương thức của aggregatehàm theo mặc định là na.action = na.omit):

ta1 <- tapply(airquality$Ozone, airquality$Month, mean, na.rm = TRUE)
ta2 <- tapply(airquality$Temp, airquality$Month, mean, na.rm = TRUE)

 cbind(ta1, ta2)
       ta1      ta2
5 23.61538 65.54839
6 29.44444 79.10000
7 59.11538 83.90323
8 59.96154 83.96774
9 31.44828 76.90000

trong khi với bychúng ta không thể đạt được điều đó trong thực tế, lệnh gọi hàm sau trả về lỗi (nhưng rất có thể nó liên quan đến hàm được cung cấp, mean):

by(airquality[c("Ozone", "Temp")], airquality$Month, mean, na.rm = TRUE)

Mặt khác, các kết quả là như nhau và sự khác biệt chỉ là trong lớp (và sau đó cách nó được hiển thị / in và không chỉ - ví dụ, làm thế nào để đặt lại nó) đối tượng:

byagg <- by(airquality[c("Ozone", "Temp")], airquality$Month, summary)
aggagg <- aggregate(cbind(Ozone, Temp) ~ Month, data = airquality, summary)

Mã trước đó đạt được cùng một mục tiêu và kết quả, tại một số điểm, công cụ nào để sử dụng chỉ là vấn đề sở thích và nhu cầu cá nhân; hai đối tượng trước có nhu cầu rất khác nhau về mặt đặt lại.


Như tôi đã nói, sức mạnh của phát sinh khi chúng ta không thể sử dụng một cách khéo léo; đoạn mã sau đây là một ví dụ: ĐÂY LÀ NHỮNG CÔNG VIỆC BẠN ĐÃ SỬ DỤNG TRÊN. Và bạn đã đưa ra một ví dụ về tính toán tóm tắt. Vâng, hãy nói rằng các số liệu thống kê tóm tắt chỉ có thể được tính toán rằng nó sẽ cần được làm sạch: ví dụ: data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))đây là việc sử dụng tapply . With the right splitting there is nothing you cant do with tapply . The only thing is it returns a matrix. Please be careful by saying we cant use tapply`
Onyambu

35

Có rất nhiều câu trả lời tuyệt vời thảo luận về sự khác biệt trong các trường hợp sử dụng cho từng chức năng. Không có câu trả lời nào thảo luận về sự khác biệt trong hiệu suất. Đó là lý do hợp lý khiến các chức năng khác nhau mong đợi đầu vào khác nhau và tạo ra đầu ra khác nhau, tuy nhiên hầu hết trong số chúng có một mục tiêu chung để đánh giá theo chuỗi / nhóm. Câu trả lời của tôi sẽ tập trung vào hiệu suất. Do ở trên, việc tạo đầu vào từ các vectơ được bao gồm trong thời gian, nên applyhàm cũng không được đo.

Tôi đã thử nghiệm hai chức năng khác nhau sumlengthcùng một lúc. Khối lượng thử nghiệm là 50M trên đầu vào và 50K trên đầu ra. Tôi cũng đã bao gồm hai gói phổ biến hiện không được sử dụng rộng rãi tại thời điểm khi câu hỏi được hỏi data.tabledplyr. Cả hai chắc chắn là đáng để xem nếu bạn đang nhắm đến hiệu suất tốt.

library(dplyr)
library(data.table)
set.seed(123)
n = 5e7
k = 5e5
x = runif(n)
grp = sample(k, n, TRUE)

timing = list()

# sapply
timing[["sapply"]] = system.time({
    lt = split(x, grp)
    r.sapply = sapply(lt, function(x) list(sum(x), length(x)), simplify = FALSE)
})

# lapply
timing[["lapply"]] = system.time({
    lt = split(x, grp)
    r.lapply = lapply(lt, function(x) list(sum(x), length(x)))
})

# tapply
timing[["tapply"]] = system.time(
    r.tapply <- tapply(x, list(grp), function(x) list(sum(x), length(x)))
)

# by
timing[["by"]] = system.time(
    r.by <- by(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# aggregate
timing[["aggregate"]] = system.time(
    r.aggregate <- aggregate(x, list(grp), function(x) list(sum(x), length(x)), simplify = FALSE)
)

# dplyr
timing[["dplyr"]] = system.time({
    df = data_frame(x, grp)
    r.dplyr = summarise(group_by(df, grp), sum(x), n())
})

# data.table
timing[["data.table"]] = system.time({
    dt = setnames(setDT(list(x, grp)), c("x","grp"))
    r.data.table = dt[, .(sum(x), .N), grp]
})

# all output size match to group count
sapply(list(sapply=r.sapply, lapply=r.lapply, tapply=r.tapply, by=r.by, aggregate=r.aggregate, dplyr=r.dplyr, data.table=r.data.table), 
       function(x) (if(is.data.frame(x)) nrow else length)(x)==k)
#    sapply     lapply     tapply         by  aggregate      dplyr data.table 
#      TRUE       TRUE       TRUE       TRUE       TRUE       TRUE       TRUE 

# print timings
as.data.table(sapply(timing, `[[`, "elapsed"), keep.rownames = TRUE
              )[,.(fun = V1, elapsed = V2)
                ][order(-elapsed)]
#          fun elapsed
#1:  aggregate 109.139
#2:         by  25.738
#3:      dplyr  18.978
#4:     tapply  17.006
#5:     lapply  11.524
#6:     sapply  11.326
#7: data.table   2.686

Có phải bình thường là dplyr thấp hơn các hàm applt?
Mostafa

1
@DimitriPetrenko Tôi không nghĩ vậy, không biết tại sao nó lại ở đây. Tốt nhất là kiểm tra dữ liệu của chính bạn, vì có nhiều yếu tố xuất hiện.
jangorecki

28

Mặc dù tất cả các câu trả lời tuyệt vời ở đây, có 2 chức năng cơ bản đáng được đề cập hơn, outerchức năng hữu ích và eapplychức năng tối nghĩa

bên ngoài

outerlà một chức năng rất hữu ích ẩn dưới dạng trần tục hơn. Nếu bạn đọc trợ giúp cho outermô tả của nó nói:

The outer product of the arrays X and Y is the array A with dimension  
c(dim(X), dim(Y)) where element A[c(arrayindex.x, arrayindex.y)] =   
FUN(X[arrayindex.x], Y[arrayindex.y], ...).

mà làm cho nó có vẻ như điều này chỉ hữu ích cho những thứ đại số tuyến tính. Tuy nhiên, nó có thể được sử dụng giống như mapplyáp dụng một hàm cho hai vectơ đầu vào. Sự khác biệt là mapplysẽ áp dụng hàm cho hai phần tử đầu tiên và sau đó là hai phần tử thứ hai, v.v., trong khi đó outersẽ áp dụng hàm cho mọi kết hợp của một phần tử từ vectơ thứ nhất và phần tử thứ hai. Ví dụ:

 A<-c(1,3,5,7,9)
 B<-c(0,3,6,9,12)

mapply(FUN=pmax, A, B)

> mapply(FUN=pmax, A, B)
[1]  1  3  6  9 12

outer(A,B, pmax)

 > outer(A,B, pmax)
      [,1] [,2] [,3] [,4] [,5]
 [1,]    1    3    6    9   12
 [2,]    3    3    6    9   12
 [3,]    5    5    6    9   12
 [4,]    7    7    7    9   12
 [5,]    9    9    9    9   12

Cá nhân tôi đã sử dụng điều này khi tôi có một vectơ giá trị và vectơ điều kiện và muốn xem giá trị nào đáp ứng điều kiện nào.

háo hức

eapplygiống như lapplyngoại trừ việc áp dụng một hàm cho mọi phần tử trong danh sách, nó áp dụng một hàm cho mọi phần tử trong một môi trường. Ví dụ: nếu bạn muốn tìm danh sách các hàm do người dùng xác định trong môi trường toàn cầu:

A<-c(1,3,5,7,9)
B<-c(0,3,6,9,12)
C<-list(x=1, y=2)
D<-function(x){x+1}

> eapply(.GlobalEnv, is.function)
$A
[1] FALSE

$B
[1] FALSE

$C
[1] FALSE

$D
[1] TRUE 

Thành thật mà nói tôi không sử dụng nó rất nhiều nhưng nếu bạn đang xây dựng nhiều gói hoặc tạo ra nhiều môi trường thì nó có thể có ích.


25

Nó có lẽ đáng được đề cập ave. avetapplyanh em họ thân thiện. Nó trả về kết quả ở dạng mà bạn có thể cắm thẳng vào khung dữ liệu của mình.

dfr <- data.frame(a=1:20, f=rep(LETTERS[1:5], each=4))
means <- tapply(dfr$a, dfr$f, mean)
##  A    B    C    D    E 
## 2.5  6.5 10.5 14.5 18.5 

## great, but putting it back in the data frame is another line:

dfr$m <- means[dfr$f]

dfr$m2 <- ave(dfr$a, dfr$f, FUN=mean) # NB argument name FUN is needed!
dfr
##   a f    m   m2
##   1 A  2.5  2.5
##   2 A  2.5  2.5
##   3 A  2.5  2.5
##   4 A  2.5  2.5
##   5 B  6.5  6.5
##   6 B  6.5  6.5
##   7 B  6.5  6.5
##   ...

Không có gì trong gói cơ sở hoạt động như aveđối với toàn bộ khung dữ liệu ( bygiống như tapplyđối với khung dữ liệu). Nhưng bạn có thể làm mờ nó:

dfr$foo <- ave(1:nrow(dfr), dfr$f, FUN=function(x) {
    x <- dfr[x,]
    sum(x$m*x$m2)
})
dfr
##     a f    m   m2    foo
## 1   1 A  2.5  2.5    25
## 2   2 A  2.5  2.5    25
## 3   3 A  2.5  2.5    25
## ...

12

Gần đây tôi đã phát hiện ra sweepchức năng khá hữu ích và thêm nó vào đây để hoàn thiện:

quét

Ý tưởng cơ bản là quét qua một mảng hàng hoặc cột khôn ngoan và trả về một mảng đã sửa đổi. Một ví dụ sẽ làm rõ điều này (nguồn: datacamp ):

Giả sử bạn có một ma trận và muốn chuẩn hóa nó theo cột:

dataPoints <- matrix(4:15, nrow = 4)

# Find means per column with `apply()`
dataPoints_means <- apply(dataPoints, 2, mean)

# Find standard deviation with `apply()`
dataPoints_sdev <- apply(dataPoints, 2, sd)

# Center the points 
dataPoints_Trans1 <- sweep(dataPoints, 2, dataPoints_means,"-")
print(dataPoints_Trans1)
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Return the result
dataPoints_Trans1
##      [,1] [,2] [,3]
## [1,] -1.5 -1.5 -1.5
## [2,] -0.5 -0.5 -0.5
## [3,]  0.5  0.5  0.5
## [4,]  1.5  1.5  1.5
# Normalize
dataPoints_Trans2 <- sweep(dataPoints_Trans1, 2, dataPoints_sdev, "/")

# Return the result
dataPoints_Trans2
##            [,1]       [,2]       [,3]
## [1,] -1.1618950 -1.1618950 -1.1618950
## [2,] -0.3872983 -0.3872983 -0.3872983
## [3,]  0.3872983  0.3872983  0.3872983
## [4,]  1.1618950  1.1618950  1.1618950

NB: đối với ví dụ đơn giản này, kết quả tương tự dĩ nhiên có thể đạt được dễ dàng hơn bằng cách
apply(dataPoints, 2, scale)


1
Điều này có liên quan đến nhóm?
Frank

2
@Frank: Vâng, thành thật mà nói với bạn, tiêu đề của bài đăng này khá sai lệch: khi bạn đọc chính câu hỏi đó là về "gia đình áp dụng". sweeplà một hàm bậc cao giống như tất cả những người khác đề cập ở đây, ví dụ như apply, sapply, lapplyVì vậy, cùng một câu hỏi có thể được hỏi về câu trả lời được chấp nhận với hơn 1.000 upvotes và các ví dụ trong đó. Chỉ cần nhìn vào ví dụ được đưa ra cho applyđó.
vonjd

2
quét có một tên gây hiểu lầm, mặc định sai lệch và tên tham số sai lệch :). Tôi dễ hiểu hơn theo cách này: 1) STATS là vectơ hoặc giá trị đơn sẽ được lặp lại để tạo thành ma trận có cùng kích thước với đầu vào đầu tiên, 2) FUN sẽ được áp dụng cho đầu vào thứ nhất và ma trận mới này. Có thể minh họa tốt hơn bởi : sweep(matrix(1:6,nrow=2),2,7:9,list). Nó thường hiệu quả hơn applyvì nơi applyvòng, sweepcó thể sử dụng chức năng vectorised.
Moody_Mudskipper

2

Trong gói thu gọn được phát hành gần đây trên CRAN, tôi đã cố gắng nén hầu hết các chức năng áp dụng phổ biến thành chỉ 2 chức năng:

  1. dapply(Áp dụng dữ liệu) áp dụng các hàm cho các hàng hoặc cột (mặc định) của ma trận và data.frames và (mặc định) trả về một đối tượng cùng loại và có cùng thuộc tính (trừ khi kết quả của mỗi phép tính là nguyên tử và drop = TRUE). Hiệu suất tương đương với lapplycác cột data.frame và nhanh hơn khoảng 2 lần so applyvới các cột hoặc cột ma trận. Song song có sẵn thông qua mclapply(chỉ dành cho MAC).

Cú pháp:

dapply(X, FUN, ..., MARGIN = 2, parallel = FALSE, mc.cores = 1L, 
       return = c("same", "matrix", "data.frame"), drop = TRUE)

Ví dụ:

# Apply to columns:
dapply(mtcars, log)
dapply(mtcars, sum)
dapply(mtcars, quantile)
# Apply to rows:
dapply(mtcars, sum, MARGIN = 1)
dapply(mtcars, quantile, MARGIN = 1)
# Return as matrix:
dapply(mtcars, quantile, return = "matrix")
dapply(mtcars, quantile, MARGIN = 1, return = "matrix")
# Same for matrices ...
  1. BYlà một tên chung S3 cho tính toán phân chia kết hợp với phương pháp vector, ma trận và data.frame. Nó nhanh hơn đáng kể so với tapply, byaggregate(cũng nhanh hơn plyr, trên dữ liệu lớn dplyrmặc dù nhanh hơn).

Cú pháp:

BY(X, g, FUN, ..., use.g.names = TRUE, sort = TRUE,
   expand.wide = FALSE, parallel = FALSE, mc.cores = 1L,
   return = c("same", "matrix", "data.frame", "list"))

Ví dụ:

# Vectors:
BY(iris$Sepal.Length, iris$Species, sum)
BY(iris$Sepal.Length, iris$Species, quantile)
BY(iris$Sepal.Length, iris$Species, quantile, expand.wide = TRUE) # This returns a matrix 
# Data.frames
BY(iris[-5], iris$Species, sum)
BY(iris[-5], iris$Species, quantile)
BY(iris[-5], iris$Species, quantile, expand.wide = TRUE) # This returns a wider data.frame
BY(iris[-5], iris$Species, quantile, return = "matrix") # This returns a matrix
# Same for matrices ...

Danh sách các biến nhóm cũng có thể được cung cấp cho g.

Nói về hiệu suất: Mục tiêu chính của sự sụp đổ là thúc đẩy lập trình hiệu suất cao trong R và vượt ra ngoài việc chia tách áp dụng kết hợp hoàn toàn. Với mục đích này gói có một bộ đầy đủ của C ++ dựa chức năng nhanh generic: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdifffgrowth. Họ thực hiện các tính toán được nhóm lại trong một lần chuyển qua dữ liệu (nghĩa là không phân tách và kết hợp lại).

Cú pháp:

fFUN(x, g = NULL, [w = NULL,] TRA = NULL, [na.rm = TRUE,] use.g.names = TRUE, drop = TRUE)

Ví dụ:

v <- iris$Sepal.Length
f <- iris$Species

# Vectors
fmean(v)             # mean
fmean(v, f)          # grouped mean
fsd(v, f)            # grouped standard deviation
fsd(v, f, TRA = "/") # grouped scaling
fscale(v, f)         # grouped standardizing (scaling and centering)
fwithin(v, f)        # grouped demeaning

w <- abs(rnorm(nrow(iris)))
fmean(v, w = w)      # Weighted mean
fmean(v, f, w)       # Weighted grouped mean
fsd(v, f, w)         # Weighted grouped standard-deviation
fsd(v, f, w, "/")    # Weighted grouped scaling
fscale(v, f, w)      # Weighted grouped standardizing
fwithin(v, f, w)     # Weighted grouped demeaning

# Same using data.frames...
fmean(iris[-5], f)                # grouped mean
fscale(iris[-5], f)               # grouped standardizing
fwithin(iris[-5], f)              # grouped demeaning

# Same with matrices ...

Trong các họa tiết gói tôi cung cấp điểm chuẩn. Lập trình với các chức năng nhanh nhanh hơn đáng kể so với lập trình với dplyr hoặc data.table , đặc biệt là trên dữ liệu nhỏ hơn, nhưng cũng trên dữ liệu lớn.

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.