Fungsi pengelompokan (tapply, by, agregat) dan * berlaku keluarga

1041

Setiap kali saya ingin melakukan sesuatu "memetakan" py dalam R, saya biasanya mencoba menggunakan fungsi dalam applykeluarga.

Namun, saya tidak pernah benar-benar memahami perbedaan di antara mereka - bagaimana { sapply,, lapplydll} menerapkan fungsi ke input / input yang dikelompokkan, seperti apa outputnya, atau bahkan seperti apa inputnya - jadi saya sering hanya melalui mereka semua sampai saya mendapatkan apa yang saya inginkan.

Adakah yang bisa menjelaskan bagaimana cara menggunakannya kapan?

Pemahaman saya saat ini (mungkin salah / tidak lengkap) adalah ...

  1. sapply(vec, f): input adalah vektor. Output adalah vektor / matriks, di mana elemen iadalah f(vec[i]), memberi Anda matriks jika fmemiliki output multi-elemen

  2. lapply(vec, f): sama dengan sapply, tetapi output adalah daftar?

  3. apply(matrix, 1/2, f): input adalah sebuah matriks. output adalah vektor, di mana elemen iadalah f (baris / kolom i dari matriks)
  4. tapply(vector, grouping, f): output adalah matriks / array, di mana elemen dalam matriks / array adalah nilai fpada pengelompokan gvektor, dan gakan didorong ke nama baris / col
  5. by(dataframe, grouping, f): biarkan gmenjadi pengelompokan. berlaku funtuk setiap kolom grup / bingkai data. cukup cetak pengelompokan dan nilai fdi setiap kolom.
  6. aggregate(matrix, grouping, f): mirip dengan by, tetapi alih-alih mencetak hasil cetak, agregat menempelkan semuanya ke dalam kerangka data.

Pertanyaan sampingan: Saya masih belum belajar plyr atau membentuk kembali - akankah plyratau reshapemengganti semuanya?

grautur
sumber
33
ke pertanyaan sampingan Anda: karena banyak hal plyr adalah pengganti langsung untuk *apply()dan by. plyr (setidaknya bagi saya) tampaknya jauh lebih konsisten karena saya selalu tahu persis format data apa yang diharapkannya dan apa yang akan dimuntahkannya. Itu menyelamatkan saya banyak kerumitan.
JD Long
12
Juga, saya akan merekomendasikan menambahkan: doBydan pilihan & menerapkan kemampuan data.table.
Iterator
7
sapplyhanya lapplydengan penambahan simplify2arraypada output. applymemang memaksa untuk vektor atom, tetapi output dapat berupa vektor atau daftar. bymembagi dataframe menjadi sub-dataframe, tetapi tidak digunakan fpada kolom secara terpisah. Hanya jika ada metode untuk kelas 'data.frame' yang dapat fditerapkan oleh kolom by. aggregateadalah metode generik sehingga berbeda untuk kelas yang berbeda dari argumen pertama.
IRTFM
8
Mnemonik: l adalah untuk 'daftar', s adalah untuk 'penyederhanaan', t adalah untuk 'per tipe' (setiap tingkat pengelompokan adalah sebuah tipe)
Lutz Prechelt
Ada juga ada beberapa fungsi dalam paket Rfast, seperti: eachcol.apply, apply.condition, dan banyak lagi, yang lebih cepat dari padanan R
Stefanos

Jawaban:

1330

R memiliki banyak * fungsi yang berlaku yang dapat dijelaskan dalam file bantuan (misalnya ?apply). Ada cukup banyak dari mereka, bahwa penggunaan awal mungkin mengalami kesulitan dalam memutuskan mana yang sesuai untuk situasi mereka atau bahkan mengingat semuanya. Mereka mungkin memiliki pengertian umum bahwa "Saya harus menggunakan * menerapkan fungsi di sini", tetapi mungkin sulit untuk menjaga semuanya tetap lurus pada awalnya.

Terlepas dari kenyataan (dicatat dalam jawaban lain) bahwa banyak fungsi * keluarga yang berlaku dicakup oleh plyrpaket yang sangat populer , fungsi-fungsi dasar tetap berguna dan patut diketahui.

Jawaban ini dimaksudkan untuk bertindak sebagai semacam rambu untuk pengguna baru untuk membantu mengarahkan mereka ke fungsi yang benar * berlaku untuk masalah khusus mereka. Catatan, ini tidak dimaksudkan hanya memuntahkan atau mengganti dokumentasi R! Harapannya adalah bahwa jawaban ini membantu Anda untuk memutuskan fungsi mana * yang sesuai dengan situasi Anda dan kemudian terserah Anda untuk merisetnya lebih lanjut. Dengan satu pengecualian, perbedaan kinerja tidak akan diatasi.

  • apply - Ketika Anda ingin menerapkan fungsi ke baris atau kolom matriks (dan analog berdimensi tinggi); umumnya tidak disarankan untuk frame data karena akan memaksa ke matriks terlebih dahulu.

    # 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

    Jika Anda ingin cara baris / kolom atau jumlah untuk matriks 2D, pastikan untuk menyelidiki sangat optimal, kilat-cepat colMeans, rowMeans, colSums, rowSums.

  • lapply - Ketika Anda ingin menerapkan fungsi pada setiap elemen daftar pada gilirannya dan mendapatkan daftar kembali.

    Ini adalah pekerja keras dari banyak fungsi * lainnya yang berlaku. Kupas kembali kode mereka dan Anda akan sering menemukan di lapplybawahnya.

    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 - Ketika Anda ingin menerapkan fungsi ke setiap elemen daftar pada gilirannya, tetapi Anda ingin vektor kembali, bukan daftar.

    Jika Anda mengetik unlist(lapply(...)), hentikan dan pertimbangkan 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 

    Dalam penggunaan yang lebih maju sapplyakan mencoba untuk memaksa hasilnya ke array multi-dimensi, jika sesuai. Misalnya, jika fungsi kami mengembalikan vektor dengan panjang yang sama, sapplyakan menggunakannya sebagai kolom dari sebuah matriks:

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

    Jika fungsi kita mengembalikan matriks 2 dimensi, sapplypada dasarnya akan melakukan hal yang sama, memperlakukan setiap matriks yang dikembalikan sebagai vektor panjang tunggal:

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

    Kecuali kita tentukan simplify = "array", dalam hal ini akan menggunakan matriks individu untuk membangun array multi-dimensi:

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

    Setiap perilaku ini tentu saja bergantung pada fungsi kita yang mengembalikan vektor atau matriks dengan panjang atau dimensi yang sama.

  • vapply - Ketika Anda ingin menggunakan sapplytetapi mungkin perlu memeras lebih cepat dari kode Anda.

    Sebab vapply, pada dasarnya Anda memberi R contoh hal apa yang akan dikembalikan fungsi Anda, yang bisa menghemat waktu untuk memaksa nilai yang dikembalikan agar sesuai dengan satu vektor atom.

    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 - Untuk ketika Anda memiliki beberapa struktur data (misalnya vektor, daftar) dan Anda ingin menerapkan fungsi ke elemen 1 masing-masing, dan kemudian elemen 2 masing-masing, dll., memaksa hasil ke vektor / array seperti di sapply.

    Ini multivarian dalam arti bahwa fungsi Anda harus menerima banyak argumen.

    #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
  • Peta - Pembungkus mapplydengan SIMPLIFY = FALSE, sehingga dijamin akan mengembalikan daftar.

    Map(sum, 1:5, 1:5, 1:5)
    [[1]]
    [1] 3
    
    [[2]]
    [1] 6
    
    [[3]]
    [1] 9
    
    [[4]]
    [1] 12
    
    [[5]]
    [1] 15
  • rapply - Untuk saat Anda ingin menerapkan fungsi ke setiap elemen struktur daftar bersarang , secara rekursif.

    Untuk memberi tahu Anda betapa tidak lazimnya rapply, saya lupa ketika pertama kali memposting jawaban ini! Jelas, saya yakin banyak orang yang menggunakannya, tapi YMMV. rapplypaling baik diilustrasikan dengan fungsi yang ditentukan pengguna untuk diterapkan:

    # 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 - Untuk saat Anda ingin menerapkan fungsi ke subset vektor dan subset ditentukan oleh beberapa vektor lain, biasanya faktor.

    Domba hitam dari keluarga * berlaku, semacam. Penggunaan file bantuan dari frasa "array kasar" bisa sedikit membingungkan , tetapi sebenarnya cukup sederhana.

    Vektor:

    x <- 1:20

    Faktor yang mendefinisikan (dengan panjang yang sama!):

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

    Tambahkan nilai-nilai di xdalam setiap subkelompok yang ditentukan oleh y:

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

    Contoh yang lebih kompleks dapat ditangani di mana subkelompok ditentukan oleh kombinasi unik dari daftar beberapa faktor. tapplyadalah semangat yang sama split-berlaku-menggabungkan fungsi yang umum dalam R ( aggregate, by, ave, ddply, dll) Oleh karena itu statusnya kambing hitam.

joran
sumber
32
Percaya Anda akan menemukan bahwa byadalah murni split-lapply dan aggregateadalah tapplypada intinya. Saya pikir domba hitam membuat kain yang bagus.
IRTFM
21
Respons yang fantastis! Ini harus menjadi bagian dari dokumentasi R resmi :). Satu saran kecil: mungkin menambahkan beberapa peluru tentang penggunaan aggregatedan byjuga? (Saya akhirnya memahaminya setelah uraian Anda !, tetapi itu cukup umum, jadi mungkin berguna untuk memisahkan dan memiliki beberapa contoh spesifik untuk kedua fungsi tersebut.)
grautur
3
@ Grutur Saya secara aktif memangkas hal-hal dari jawaban ini untuk menghindari itu menjadi (a) terlalu lama dan (b) menulis ulang dokumentasi. Saya memutuskan bahwa sementara aggregate,, bydll didasarkan pada * menerapkan fungsi, cara Anda mendekati menggunakannya cukup berbeda dari perspektif pengguna sehingga mereka harus dirangkum dalam jawaban yang terpisah. Saya mungkin berusaha jika saya punya waktu, atau mungkin orang lain akan mengalahkan saya untuk itu dan mendapatkan upvote saya.
joran
4
juga, ?Mapsebagai kerabatmapply
baptiste
3
@ jsanders - Saya tidak akan setuju dengan itu sama sekali. data.frames adalah bagian yang sangat penting dari R dan sebagai listobjek yang sering dimanipulasi dengan menggunakan lapplykhususnya. Mereka juga bertindak sebagai wadah untuk mengelompokkan vektor / faktor dari banyak jenis bersama-sama dalam dataset persegi panjang tradisional. Sementara data.tabledan plyrmungkin menambahkan jenis sintaks tertentu yang beberapa mungkin merasa lebih nyaman, mereka memperluas dan bertindak data.framemasing-masing.
thelatemail
191

Di samping catatan, berikut adalah bagaimana berbagai plyrfungsi sesuai dengan *applyfungsi dasar (dari pengantar ke dokumen plyr dari halaman 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 

Salah satu tujuannya plyradalah untuk memberikan konvensi penamaan yang konsisten untuk setiap fungsi, menyandikan tipe data input dan output dalam nama fungsi. Ini juga memberikan konsistensi dalam output, di mana output dari dlply()mudah dilalui ldply()untuk menghasilkan output yang bermanfaat, dll.

Secara konseptual, belajar plyrtidak lebih sulit daripada memahami *applyfungsi-fungsi dasar .

plyrdan reshapefungsi telah menggantikan hampir semua fungsi ini dalam penggunaan saya setiap hari. Tapi, juga dari dokumen Intro ke Plyr:

Fungsi terkait tapplydan sweeptidak memiliki fungsi yang sesuai plyr, dan tetap bermanfaat. mergeberguna untuk menggabungkan ringkasan dengan data asli.

JoFrhwld
sumber
13
Ketika saya mulai belajar R dari awal saya menemukan plyr JAUH lebih mudah dipelajari daripada *apply()keluarga fungsi. Bagi saya, ddply()sangat intuitif karena saya terbiasa dengan fungsi agregasi SQL. ddply()menjadi palu saya untuk memecahkan banyak masalah, beberapa di antaranya bisa diselesaikan dengan lebih baik dengan perintah lain.
JD Long
1
Saya kira saya berpikir bahwa konsep di balik plyrfungsi mirip dengan *applyfungsi, jadi jika Anda bisa melakukan satu, Anda bisa melakukan yang lain, tetapi plyrfungsi lebih mudah diingat. Tapi saya sepenuhnya setuju dengan ddply()palu!
JoFrhwld
1
Paket plyr memiliki join()fungsi yang melakukan tugas-tugas yang mirip dengan penggabungan. Mungkin lebih tepat untuk menyebutkannya dalam konteks plyr.
marbel
Janganlah kita melupakan yang miskin, dilupakaneapply
JDL
Jawaban yang bagus secara umum, tapi saya pikir itu meremehkan utilitas vapplydan kelemahannya sapply. Keuntungan utama vapplyadalah bahwa ia menegakkan tipe dan panjang keluaran, sehingga Anda akan berakhir dengan output yang diharapkan secara tepat atau kesalahan informatif. Di sisi lain, sapplyakan mencoba menyederhanakan output mengikuti aturan yang tidak selalu jelas, dan kembali ke daftar sebaliknya. Sebagai contoh, cobalah untuk memprediksi jenis output ini akan menghasilkan: sapply(list(1:5, 6:10, matrix(1:4, 2)), function(x) head(x, 1)). Bagaimana dengan sapply(list(matrix(1:4, 2), matrix(1:4, 2)), ...)?
Alexey Shiklomanov
133

Dari slide 21 dari http://www.slideshare.net/hadley/plyr-one-data-analytic-strategy :

terapkan, sapply, lapply, by, agregat

(Semoga jelas bahwa applyberkorespondensi dengan @ Hadley's aaplydan aggregateberkorespondensi dengan @ Hadley's, ddplydll. Slide 20 dari slideshare yang sama akan menjelaskan jika Anda tidak mendapatkannya dari gambar ini.)

(di sebelah kiri adalah input, di atas adalah output)

isomorfisma
sumber
4
apakah ada kesalahan ketik pada slide? Sel kiri atas harus aaply
JHowIX
100

Pertama-tama mulailah dengan jawaban Joran yang luar biasa - keraguan apa pun bisa lebih baik dari itu.

Kemudian mnemonik berikut dapat membantu untuk mengingat perbedaan antara masing-masing. Sementara beberapa jelas, yang lain mungkin kurang begitu --- untuk ini Anda akan menemukan pembenaran dalam diskusi Joran.

Ilmu tentang cara menghafal

  • lapplyadalah daftar yang berlaku yang bertindak pada daftar atau vektor dan mengembalikan daftar.
  • sapplyadalah sederhana lapply (default fungsi untuk kembali vektor atau matriks bila memungkinkan)
  • vapplyadalah terverifikasi yang berlaku (memungkinkan tipe objek kembali yang ditentukan sebelumnya)
  • rapplyadalah aplikasi rekursif untuk daftar bersarang, yaitu daftar dalam daftar
  • tapplyadalah tag yang berlaku di mana tag mengidentifikasi subset
  • apply adalah generik : berlaku fungsi untuk baris atau kolom matriks (atau, lebih umum, untuk dimensi array)

Membangun Latar Belakang yang Tepat

Jika menggunakan applykeluarga itu masih terasa asing bagi Anda, mungkin Anda kehilangan sudut pandang kunci.

Dua artikel ini dapat membantu. Mereka memberikan latar belakang yang diperlukan untuk memotivasi teknik pemrograman fungsional yang disediakan oleh applykeluarga fungsi.

Pengguna Lisp akan mengenali paradigma dengan segera. Jika Anda tidak terbiasa dengan Lisp, begitu Anda mengenal FP, Anda akan memiliki sudut pandang yang kuat untuk digunakan dalam R - dan applyakan jauh lebih masuk akal.

Assad Ebrahim
sumber
51

Karena saya menyadari bahwa (sangat bagus) jawaban dari kurangnya posting bydan aggregatepenjelasan. Ini kontribusi saya.

OLEH

The byfungsi, seperti yang dinyatakan dalam dokumentasi dapat meskipun, sebagai "pembungkus" untuk tapply. Kekuatan bymuncul ketika kita ingin menghitung tugas yang tapplytidak bisa ditangani. Salah satu contohnya adalah kode ini:

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 

Jika kita mencetak dua objek ini, ctdan cb, kita "pada dasarnya" memiliki hasil yang sama dan satu-satunya perbedaan adalah bagaimana mereka ditampilkan dan classatribut yang berbeda , masing by- masing untuk cbdan arrayuntuk ct.

Seperti yang telah saya katakan, kekuatan bymuncul ketika kita tidak dapat menggunakan tapply; kode berikut adalah salah satu contoh:

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

R mengatakan bahwa argumen harus memiliki panjang yang sama, katakan "kita ingin menghitung summarysemua variabel di irissepanjang faktor Species": tetapi R tidak bisa melakukan itu karena tidak tahu cara menangani.

Dengan byfungsi R memberangkatkan metode khusus untuk data framekelas dan kemudian membiarkan summaryfungsi tersebut bekerja bahkan jika panjang argumen pertama (dan tipenya juga) berbeda.

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     

ini memang berhasil dan hasilnya sangat mengejutkan. Ini adalah objek kelas byyang sepanjang Species(katakanlah, untuk masing-masing dari mereka) menghitung summarysetiap variabel.

Perhatikan bahwa jika argumen pertama adalah a data frame, fungsi yang dikirim harus memiliki metode untuk kelas objek tersebut. Sebagai contoh adalah kita menggunakan kode ini dengan meanfungsi kita akan memiliki kode ini yang tidak masuk akal sama sekali:

 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

AGREGAT

aggregatedapat dilihat sebagai cara penggunaan yang berbeda tapplyjika kita menggunakannya sedemikian rupa.

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

Dua perbedaan langsung adalah bahwa argumen kedua aggregate harus daftar sementara tapply bisa (tidak wajib) menjadi daftar dan bahwa output aggregateadalah bingkai data sedangkan yang salah tapplyadalah array.

Kekuatannya aggregateadalah ia dapat menangani subset data dengan mudah dengan subsetargumen dan memiliki metode untuk tsobjek dan formulajuga.

Elemen-elemen ini aggregatememudahkan untuk bekerja dengan itu tapplydalam beberapa situasi. Berikut adalah beberapa contoh (tersedia dalam dokumentasi):

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

Kita dapat mencapai hal yang sama dengan tapplytetapi sintaks sedikit lebih sulit dan output (dalam beberapa keadaan) kurang dapat dibaca:

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

Ada saat-saat lain ketika kita tidak dapat menggunakan byatau tapplydan kita harus menggunakannya 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

Kami tidak dapat memperoleh hasil sebelumnya dengan tapplysatu panggilan, tetapi kami harus menghitung rata-rata Monthuntuk setiap elemen dan kemudian menggabungkannya (juga perhatikan bahwa kami harus memanggil na.rm = TRUE, karena formulametode aggregatefungsi secara default 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

sementara dengan bykita tidak bisa mencapai itu pada kenyataannya panggilan fungsi berikut mengembalikan kesalahan (tetapi kemungkinan besar itu terkait dengan fungsi yang disediakan, mean):

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

Lain kali hasilnya sama dan perbedaannya hanya di kelas (dan kemudian bagaimana itu ditampilkan / dicetak dan tidak hanya - contoh, cara subset itu) objek:

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

Kode sebelumnya mencapai tujuan dan hasil yang sama, pada beberapa titik alat apa yang digunakan hanyalah masalah selera dan kebutuhan pribadi; dua objek sebelumnya memiliki kebutuhan yang sangat berbeda dalam hal pengaturan ulang.

SabDeM
sumber
Seperti yang telah saya katakan, kekuatan oleh muncul ketika kita tidak dapat menggunakan dengan sentuhan; kode berikut adalah salah satu contoh: INI ADALAH KATA-KATA YANG ANDA TELAH DIGUNAKAN DI ATAS. Dan Anda telah memberikan contoh perhitungan ringkasan. Baiklah katakanlah bahwa ringkasan statistik hanya dapat dihitung yang perlu dibersihkan: mis. data.frame(tapply(unlist(iris[,-5]),list(rep(iris[,5],ncol(iris[-5])),col(iris[-5])),summary))Ini adalah penggunaan 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

Ada banyak jawaban hebat yang membahas perbedaan dalam kasus penggunaan untuk setiap fungsi. Tidak ada jawaban yang membahas perbedaan kinerja. Itu wajar karena berbagai fungsi mengharapkan berbagai input dan menghasilkan berbagai output, namun kebanyakan dari mereka memiliki tujuan umum bersama untuk mengevaluasi secara seri / kelompok. Jawaban saya akan fokus pada kinerja. Karena di atas penciptaan input dari vektor termasuk dalam pengaturan waktu, juga applyfungsi tidak diukur.

Saya telah menguji dua fungsi yang berbeda sumdan lengthsekaligus. Volume yang diuji adalah 50M pada input dan 50K pada output. Saya juga menyertakan dua paket populer saat ini yang tidak banyak digunakan pada saat pertanyaan diajukan, data.tabledan dplyr. Keduanya pasti layak dilihat jika Anda mengincar kinerja yang baik.

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
jangorecki
sumber
Apakah normal jika dplyr lebih rendah dari fungsi applt?
Mostafa
1
@ DimitriPetrenko Saya tidak berpikir begitu, tidak yakin mengapa itu ada di sini. Cara terbaik untuk menguji data Anda sendiri, karena ada banyak faktor yang ikut bermain.
jangorecki
28

Terlepas dari semua jawaban hebat di sini, ada 2 fungsi dasar lagi yang layak disebutkan, yang bermanfaat outer fungsi dan jelas eapplyfungsi

luar

outeradalah fungsi yang sangat berguna disembunyikan sebagai yang lebih biasa. Jika Anda membaca bantuan untuk outerdeskripsinya mengatakan:

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], ...).

yang membuatnya tampak seperti ini hanya berguna untuk hal-hal jenis aljabar linier. Namun, dapat digunakan seperti mapplymenerapkan fungsi ke dua vektor input. Perbedaannya adalah bahwa mapplyakan menerapkan fungsi ke dua elemen pertama dan kemudian kedua kedua dll, sedangkan outerakan menerapkan fungsi untuk setiap kombinasi satu elemen dari vektor pertama dan satu dari yang kedua. Sebagai contoh:

 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

Saya secara pribadi menggunakan ini ketika saya memiliki vektor nilai dan vektor kondisi dan ingin melihat nilai mana yang memenuhi kondisi mana.

dengan mudah

eapplyseperti lapplykecuali bahwa alih-alih menerapkan fungsi ke setiap elemen dalam daftar, itu berlaku fungsi untuk setiap elemen dalam lingkungan. Misalnya jika Anda ingin menemukan daftar fungsi yang ditentukan pengguna di lingkungan global:

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 

Terus terang saya tidak menggunakan ini terlalu banyak tetapi jika Anda membangun banyak paket atau membuat banyak lingkungan mungkin berguna.

John Paul
sumber
25

Mungkin perlu disebutkan ave. aveadalah tapplysepupu ramah. Ini mengembalikan hasil dalam bentuk yang dapat Anda pasang langsung kembali ke bingkai data Anda.

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
##   ...

Tidak ada dalam paket dasar yang berfungsi seperti aveuntuk seluruh frame data (seperti byhalnya tapplyuntuk frame data). Tapi Anda bisa memperdayainya:

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
## ...

sumber
12

Saya baru-baru menemukan yang agak berguna sweep dan menambahkannya di sini demi kelengkapan:

menyapu

Ide dasarnya adalah menyapu melalui array baris atau kolom dan mengembalikan array yang dimodifikasi. Contoh akan memperjelas ini (sumber: datacamp ):

Katakanlah Anda memiliki matriks dan ingin membakukannya dengan bijaksana kolom:

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: untuk contoh sederhana ini hasil yang sama tentu saja dapat dicapai dengan lebih mudah
apply(dataPoints, 2, scale)

vonjd
sumber
1
Apakah ini terkait dengan pengelompokan?
Frank
2
@ Frank: Yah, jujur ​​saja dengan Anda, judul posting ini agak menyesatkan: ketika Anda membaca pertanyaan itu sendiri itu adalah tentang "keluarga yang berlaku". sweepadalah fungsi tingkat tinggi seperti yang lain yang disebutkan di sini, misalnya apply, sapply, lapplyJadi pertanyaan yang sama bisa ditanya tentang jawaban yang diterima dengan lebih dari 1.000 upvotes dan contoh yang diberikan di dalamnya. Lihat saja contoh yang diberikan di applysana.
vonjd
2
sweep memiliki nama yang menyesatkan, standar yang menyesatkan, dan nama parameter yang menyesatkan :). Lebih mudah bagi saya untuk memahaminya dengan cara ini: 1) STATS adalah vektor atau nilai tunggal yang akan diulang untuk membentuk matriks dengan ukuran yang sama dengan input pertama, 2) FUN akan diterapkan pada input pertama dan matriks baru ini. Mungkin lebih baik diilustrasikan oleh: sweep(matrix(1:6,nrow=2),2,7:9,list). Ini biasanya lebih efisien daripada applykarena di mana applyloop, sweepdapat menggunakan fungsi vektor.
Moody_Mudskipper
2

Dalam paket runtuh yang baru-baru ini dirilis di CRAN, saya telah mencoba untuk mengkompres sebagian besar fungsi yang berlaku umum menjadi hanya 2 fungsi:

  1. dapply(Data-Apply) menerapkan fungsi ke baris atau kolom (standar) dari matriks dan data.frame dan (default) mengembalikan objek dengan tipe yang sama dan dengan atribut yang sama (kecuali jika hasil dari setiap perhitungan adalah atom dan drop = TRUE). Kinerja ini sebanding dengan lapplyuntuk kolom data.frame, dan sekitar 2x lebih cepat daripada applyuntuk baris atau kolom matriks. Paralelisme tersedia melalui mclapply(hanya untuk MAC).

Sintaksis:

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

Contoh:

# 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. BYadalah generik S3 untuk komputasi split-apply-menggabungkan dengan metode vektor, matriks dan data.frame. Ini secara signifikan lebih cepat daripada tapply, bydan aggregate(juga lebih cepat daripada plyr, pada data besar dplyrlebih cepat).

Sintaksis:

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

Contoh:

# 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 ...

Daftar variabel pengelompokan juga dapat diberikan g.

Berbicara tentang kinerja: Tujuan utama keruntuhan adalah untuk mendorong pemrograman berkinerja tinggi di R dan untuk bergerak melampaui split-apply-menggabungkan semuanya. Untuk tujuan ini paket memiliki set lengkap C ++ berdasarkan fungsi cepat generik: fmean, fmedian, fmode, fsum, fprod, fsd, fvar, fmin, fmax, ffirst, flast, fNobs, fNdistinct, fscale, fbetween, fwithin, fHDbetween, fHDwithin, flag, fdiffdanfgrowth . Mereka melakukan perhitungan yang dikelompokkan dalam satu melewati data (yaitu tidak ada pemisahan dan penggabungan kembali).

Sintaksis:

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

Contoh:

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

Dalam sketsa paket saya memberikan tolok ukur. Pemrograman dengan fungsi cepat secara signifikan lebih cepat daripada pemrograman dengan dplyr atau data . Tabel , terutama pada data yang lebih kecil, tetapi juga pada data besar.

Sebastian
sumber