Menerapkan fungsi ke setiap baris tabel menggunakan dplyr?

121

Saat bekerja dengan plyrsaya sering merasa berguna untuk digunakan adplyuntuk fungsi skalar yang harus saya terapkan ke setiap baris.

misalnya

data(iris)
library(plyr)
head(
     adply(iris, 1, transform , Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     5.1
2          4.9         3.0          1.4         0.2  setosa     4.9
3          4.7         3.2          1.3         0.2  setosa     4.7
4          4.6         3.1          1.5         0.2  setosa     4.6
5          5.0         3.6          1.4         0.2  setosa     5.0
6          5.4         3.9          1.7         0.4  setosa     5.4

Sekarang saya menggunakan dplyrlebih banyak, saya bertanya-tanya apakah ada cara yang rapi / alami untuk melakukan ini? Karena ini BUKAN yang saya inginkan:

library(dplyr)
head(
     mutate(iris, Max.Len= max(Sepal.Length,Petal.Length))
    )
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species Max.Len
1          5.1         3.5          1.4         0.2  setosa     7.9
2          4.9         3.0          1.4         0.2  setosa     7.9
3          4.7         3.2          1.3         0.2  setosa     7.9
4          4.6         3.1          1.5         0.2  setosa     7.9
5          5.0         3.6          1.4         0.2  setosa     7.9
6          5.4         3.9          1.7         0.4  setosa     7.9
Stephen Henderson
sumber
Saya baru-baru ini bertanya apakah ada yang setara dengan mdplydi dplyr, dan hadley menyarankan bahwa mereka mungkin membuat sesuatu berdasarkan do. Saya kira itu juga akan berhasil di sini.
baptiste
4
Akhirnya dplyr akan memiliki sesuatu seperti rowwise()yang akan dikelompokkan berdasarkan setiap baris
hadley
@hadley thx, bukankah seharusnya itu berperilaku seperti adplysaat Anda tidak menggunakan pengelompokan? karena fungsinya yang terintegrasi erat disebut group_byNOTsplit_by
Stephen Henderson
@StephenHenderson no, karena Anda juga memerlukan beberapa cara untuk mengoperasikan tabel secara keseluruhan.
hadley
1
@HowYaDoing Ya, tetapi metode itu tidak menggeneralisasi. Tidak ada psum, pmean atau pmedian misalnya.
Stephen Henderson

Jawaban:

202

Sejak dplyr 0.2 (menurut saya) rowwise()diimplementasikan, jadi jawaban untuk masalah ini menjadi:

iris %>% 
  rowwise() %>% 
  mutate(Max.Len= max(Sepal.Length,Petal.Length))

Bukan rowwisealternatif

Lima tahun kemudian (!) Jawaban ini masih banyak trafik. Sejak diberikan, rowwisesemakin tidak disarankan, meskipun banyak orang tampaknya menganggapnya intuitif. Bantulah diri Anda sendiri dan ikuti alur kerja Jenny Bryan yang berorientasi pada baris di R dengan materi rapi untuk mendapatkan pegangan yang baik tentang topik ini.

Cara paling mudah yang saya temukan didasarkan pada salah satu contoh Hadley yang menggunakan pmap:

iris %>% 
  mutate(Max.Len= purrr::pmap_dbl(list(Sepal.Length, Petal.Length), max))

Dengan menggunakan pendekatan ini, Anda bisa memberikan sejumlah argumen ke function ( .f) di dalamnya pmap.

pmap adalah pendekatan konseptual yang baik karena merefleksikan fakta bahwa ketika Anda melakukan operasi baris bijak Anda sebenarnya bekerja dengan tupel dari daftar vektor (kolom dalam kerangka data).

alexwhan
sumber
Saya telah mengubah ini (dari atas) menjadi jawaban yang ideal karena menurut saya ini adalah penggunaan yang dimaksudkan.
Stephen Henderson
1
apakah mungkin untuk menambahkan nilai-nilai dari kerangka data yang dibentuk secara dinamis? Jadi dalam bingkai data ini nama kolom tidak diketahui. Saya bisa menambahkan jika nama kolom diketahui.
Arun Raja
stackoverflow.com/questions/28807266/… baru saja menemukan jawabannya. Dalam hal ini mereka menggunakan korelasi, bukan penjumlahan. Tapi konsepnya sama.
Arun Raja
13
Jika tidak berhasil, pastikan Anda benar-benar menggunakan dplyr :: mutate not plyr :: mutate - drove me nuts
jan-glx
Terima kasih YAK, ini menggigit saya juga. Jika Anda memasukkan keduanya plyrdan dplyrpaket, Anda hampir pasti menggunakan kesalahan mutatekecuali Anda secara eksplisit memberikan ruang lingkup dplyr::mutate.
Chris Warth
22

Pendekatan idiomatik akan membuat fungsi vektorisasi yang tepat.

Rmenyediakan pmaxyang sesuai di sini, namun juga menyediakan Vectorizesebagai pembungkus untuk mapplymemungkinkan Anda membuat versi arbitrer yang di-vectorisasi dari fungsi arbitrer.

library(dplyr)
# use base R pmax (vectorized in C)
iris %>% mutate(max.len = pmax(Sepal.Length, Petal.Length))
# use vectorize to create your own function
# for example, a horribly inefficient get first non-Na value function
# a version that is not vectorized
coalesce <- function(a,b) {r <- c(a[1],b[1]); r[!is.na(r)][1]}
# a vectorized version
Coalesce <- Vectorize(coalesce, vectorize.args = c('a','b'))
# some example data
df <- data.frame(a = c(1:5,NA,7:10), b = c(1:3,NA,NA,6,NA,10:8))
df %>% mutate(ab =Coalesce(a,b))

Perhatikan bahwa mengimplementasikan vektorisasi di C / C ++ akan lebih cepat, tetapi tidak ada magicPonypaket yang akan menulis fungsinya untuk Anda.

mnel
sumber
thx, ini adalah jawaban yang bagus, sangat bagus gaya R umum -idiomatik seperti yang Anda katakan, tapi saya rasa ini tidak benar-benar menjawab pertanyaan saya apakah ada dplyrcara ... karena akan lebih sederhana tanpa dplyr misalnya with(df, Coalesce(a,b))Mungkin, itu adalah jenis jawaban meskipun - jangan gunakan dplyruntuk itu?
Stephen Henderson
4
Harus kuakui, aku sudah mengecek ulang bahwa tidak ada magicPonypaket. Sayang sekali
rsoren
21

Anda perlu mengelompokkan berdasarkan baris:

iris %>% group_by(1:n()) %>% mutate(Max.Len= max(Sepal.Length,Petal.Length))

Inilah yang 1dilakukan di adply.

BrodieG
sumber
Sepertinya harus ada sintaks yang lebih sederhana atau "lebih baik".
Stephen Henderson
@ StephenHenderson, mungkin ada, saya bukan dplyrahli. Semoga orang lain datang dengan sesuatu yang lebih baik. Perhatikan saya membersihkannya sedikit dengan 1:n().
BrodieG
Saya curiga Anda benar, tetapi saya merasa bahwa perilaku default tanpa pengelompokan harus seperti group_by(1:n())perilaku tersebut. Jika tidak ada yang punya ide lain di pagi hari, saya akan mencentang ide Anda;)
Stephen Henderson
Juga, perhatikan bahwa ini agak bertentangan dengan dokumentasi karena n: "Fungsi ini diimplementasikan khusus untuk setiap sumber data dan hanya dapat digunakan dari dalam ringkasan.", Meskipun tampaknya berfungsi.
BrodieG
Dapatkah Anda mengacu pada Sepal.Length dan Petal.Length dengan nomor indeksnya? Jika Anda memiliki banyak variabel, itu akan berguna. Seperti ... Max.len = max ([c (1,3)])?
Rasmus Larsen
19

Perbarui 2017-08-03

Setelah menulis ini, Hadley mengubah beberapa hal lagi. Fungsi yang dulu ada di purrr sekarang ada dalam paket campuran baru yang disebut purrrlyr , yang dijelaskan sebagai:

purrrlyr berisi beberapa fungsi yang terletak di persimpangan purrr dan dplyr. Mereka telah dihapus dari purrr untuk membuat paket lebih ringan dan karena telah diganti dengan solusi lain di tidyverse.

Jadi, Anda perlu menginstal + memuat paket itu agar kode di bawah ini berfungsi.

Posting asli

Hadley sering berubah pikiran tentang apa yang harus kita gunakan, tetapi saya pikir kita harus beralih ke fungsi di purrr untuk mendapatkan fungsi baris. Setidaknya, mereka menawarkan fungsionalitas yang sama dan memiliki antarmuka yang hampir sama seperti adplydari plyr .

Ada dua fungsi terkait, by_rowdan invoke_rows. Pemahaman saya adalah bahwa Anda menggunakan by_rowketika Anda ingin mengulang baris dan menambahkan hasilnya ke data.frame. invoke_rowsdigunakan saat Anda mengulang baris data.frame dan meneruskan setiap kolom sebagai argumen ke suatu fungsi. Kami hanya akan menggunakan yang pertama.

Contoh

library(tidyverse)

iris %>% 
  by_row(..f = function(this_row) {
    browser()
  })

Ini memungkinkan kita melihat bagian dalamnya (sehingga kita dapat melihat apa yang kita lakukan), yang sama dengan melakukannya adply.

Called from: ..f(.d[[i]], ...)
Browse[1]> this_row
# A tibble: 1 × 5
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
         <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
1          5.1         3.5          1.4         0.2  setosa
Browse[1]> Q

Secara default, by_rowmenambahkan kolom daftar berdasarkan output:

iris %>% 
  by_row(..f = function(this_row) {
      this_row[1:4] %>% unlist %>% mean
  })

memberikan:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species      .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>    <list>
1           5.1         3.5          1.4         0.2  setosa <dbl [1]>
2           4.9         3.0          1.4         0.2  setosa <dbl [1]>
3           4.7         3.2          1.3         0.2  setosa <dbl [1]>
4           4.6         3.1          1.5         0.2  setosa <dbl [1]>
5           5.0         3.6          1.4         0.2  setosa <dbl [1]>
6           5.4         3.9          1.7         0.4  setosa <dbl [1]>
7           4.6         3.4          1.4         0.3  setosa <dbl [1]>
8           5.0         3.4          1.5         0.2  setosa <dbl [1]>
9           4.4         2.9          1.4         0.2  setosa <dbl [1]>
10          4.9         3.1          1.5         0.1  setosa <dbl [1]>
# ... with 140 more rows

jika sebaliknya kita mengembalikan a data.frame, kita mendapatkan daftar dengan data.frames:

iris %>% 
  by_row( ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

memberikan:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species                 .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr>               <list>
1           5.1         3.5          1.4         0.2  setosa <data.frame [1 × 2]>
2           4.9         3.0          1.4         0.2  setosa <data.frame [1 × 2]>
3           4.7         3.2          1.3         0.2  setosa <data.frame [1 × 2]>
4           4.6         3.1          1.5         0.2  setosa <data.frame [1 × 2]>
5           5.0         3.6          1.4         0.2  setosa <data.frame [1 × 2]>
6           5.4         3.9          1.7         0.4  setosa <data.frame [1 × 2]>
7           4.6         3.4          1.4         0.3  setosa <data.frame [1 × 2]>
8           5.0         3.4          1.5         0.2  setosa <data.frame [1 × 2]>
9           4.4         2.9          1.4         0.2  setosa <data.frame [1 × 2]>
10          4.9         3.1          1.5         0.1  setosa <data.frame [1 × 2]>
# ... with 140 more rows

Bagaimana kita menambahkan output dari fungsi ini dikendalikan oleh .collateparam. Ada tiga opsi: daftar, baris, kolom. Ketika keluaran kita memiliki panjang 1, tidak masalah apakah kita menggunakan baris atau kolom.

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    this_row[1:4] %>% unlist %>% mean
  })

keduanya menghasilkan:

# A tibble: 150 × 6
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .out
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <dbl>
1           5.1         3.5          1.4         0.2  setosa 2.550
2           4.9         3.0          1.4         0.2  setosa 2.375
3           4.7         3.2          1.3         0.2  setosa 2.350
4           4.6         3.1          1.5         0.2  setosa 2.350
5           5.0         3.6          1.4         0.2  setosa 2.550
6           5.4         3.9          1.7         0.4  setosa 2.850
7           4.6         3.4          1.4         0.3  setosa 2.425
8           5.0         3.4          1.5         0.2  setosa 2.525
9           4.4         2.9          1.4         0.2  setosa 2.225
10          4.9         3.1          1.5         0.1  setosa 2.400
# ... with 140 more rows

Jika kita mengeluarkan data.frame dengan 1 baris, itu hanya sedikit yang kita gunakan:

iris %>% 
  by_row(.collate = "cols", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
      )
  })

iris %>% 
  by_row(.collate = "rows", ..f = function(this_row) {
    data.frame(
      new_col_mean = this_row[1:4] %>% unlist %>% mean,
      new_col_median = this_row[1:4] %>% unlist %>% median
    )
  })

keduanya memberi:

# A tibble: 150 × 8
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  .row new_col_mean new_col_median
          <dbl>       <dbl>        <dbl>       <dbl>  <fctr> <int>        <dbl>          <dbl>
1           5.1         3.5          1.4         0.2  setosa     1        2.550           2.45
2           4.9         3.0          1.4         0.2  setosa     2        2.375           2.20
3           4.7         3.2          1.3         0.2  setosa     3        2.350           2.25
4           4.6         3.1          1.5         0.2  setosa     4        2.350           2.30
5           5.0         3.6          1.4         0.2  setosa     5        2.550           2.50
6           5.4         3.9          1.7         0.4  setosa     6        2.850           2.80
7           4.6         3.4          1.4         0.3  setosa     7        2.425           2.40
8           5.0         3.4          1.5         0.2  setosa     8        2.525           2.45
9           4.4         2.9          1.4         0.2  setosa     9        2.225           2.15
10          4.9         3.1          1.5         0.1  setosa    10        2.400           2.30
# ... with 140 more rows

kecuali bahwa yang kedua memiliki kolom yang dipanggil .rowdan yang pertama tidak.

Akhirnya, jika keluaran kita lebih panjang dari panjang 1 baik sebagai a vectoratau sebagai data.framedengan baris, maka penting apakah kita menggunakan baris atau kolom untuk .collate:

mtcars[1:2] %>% by_row(function(x) 1:5)
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "rows")
mtcars[1:2] %>% by_row(function(x) 1:5, .collate = "cols")

menghasilkan, masing-masing:

# A tibble: 32 × 3
     mpg   cyl      .out
   <dbl> <dbl>    <list>
1   21.0     6 <int [5]>
2   21.0     6 <int [5]>
3   22.8     4 <int [5]>
4   21.4     6 <int [5]>
5   18.7     8 <int [5]>
6   18.1     6 <int [5]>
7   14.3     8 <int [5]>
8   24.4     4 <int [5]>
9   22.8     4 <int [5]>
10  19.2     6 <int [5]>
# ... with 22 more rows

# A tibble: 160 × 4
     mpg   cyl  .row  .out
   <dbl> <dbl> <int> <int>
1     21     6     1     1
2     21     6     1     2
3     21     6     1     3
4     21     6     1     4
5     21     6     1     5
6     21     6     2     1
7     21     6     2     2
8     21     6     2     3
9     21     6     2     4
10    21     6     2     5
# ... with 150 more rows

# A tibble: 32 × 7
     mpg   cyl .out1 .out2 .out3 .out4 .out5
   <dbl> <dbl> <int> <int> <int> <int> <int>
1   21.0     6     1     2     3     4     5
2   21.0     6     1     2     3     4     5
3   22.8     4     1     2     3     4     5
4   21.4     6     1     2     3     4     5
5   18.7     8     1     2     3     4     5
6   18.1     6     1     2     3     4     5
7   14.3     8     1     2     3     4     5
8   24.4     4     1     2     3     4     5
9   22.8     4     1     2     3     4     5
10  19.2     6     1     2     3     4     5
# ... with 22 more rows

Jadi, intinya. Jika Anda menginginkan adply(.margins = 1, ...)fungsionalitasnya, Anda dapat menggunakan by_row.

CoderGuy123
sumber
2
by_rowtidak digunakan lagi, memanggilnya untuk mengatakan "gunakan kombinasi dari: tidyr :: nest (); dplyr :: mutate (); purrr :: map ()" github.com/hadley/purrrlyr/blob/…
momeara
Itu banyak sekali.
qwr
14

Memperluas jawaban BrodieG,

Jika fungsi mengembalikan lebih dari satu baris, maka sebagai gantinya mutate(), do()harus digunakan. Kemudian untuk menggabungkannya kembali, gunakan rbind_all()dari dplyrpaket.

Dalam dplyrversi dplyr_0.1.2, menggunakan 1:n()dalam group_by()klausul tidak berhasil untuk saya. Semoga Hadleyrowwise() segera diimplementasikan .

iris %>%
    group_by(1:nrow(iris)) %>%
    do(do_fn) %>%
    rbind_all()

Menguji kinerja,

library(plyr)    # plyr_1.8.4.9000
library(dplyr)   # dplyr_0.8.0.9000
library(purrr)   # purrr_0.2.99.9000
library(microbenchmark)

d1_count <- 1000
d2_count <- 10

d1 <- data.frame(a=runif(d1_count))

do_fn <- function(row){data.frame(a=row$a, b=runif(d2_count))}
do_fn2 <- function(a){data.frame(a=a, b=runif(d2_count))}

op <- microbenchmark(
        plyr_version = plyr::adply(d1, 1, do_fn),
        dplyr_version = d1 %>%
            dplyr::group_by(1:nrow(d1)) %>%
            dplyr::do(do_fn(.)) %>%
            dplyr::bind_rows(),
        purrr_version = d1 %>% purrr::pmap_dfr(do_fn2),
        times=50)

itu memiliki hasil sebagai berikut:

Unit: milliseconds
          expr       min        lq      mean    median        uq       max neval
  plyr_version 1227.2589 1275.1363 1317.3431 1293.5759 1314.4266 1616.5449    50
 dplyr_version  977.3025 1012.6340 1035.9436 1025.6267 1040.5882 1449.0978    50
 purrr_version  609.5790  629.7565  643.8498  644.2505  656.1959  686.8128    50

Ini menunjukkan bahwa purrrversi baru adalah yang tercepat

momeara
sumber
1

Sesuatu seperti ini?

iris$Max.Len <- pmax(iris$Sepal.Length, iris$Petal.Length)
colcarroll
sumber
1
Ya terima kasih, itu jawaban yang sangat spesifik. Tetapi contoh dan pertanyaan saya mencoba mencari tahu apakah ada dplyrsolusi umum untuk fungsi skalar apa pun.
Stephen Henderson
Secara umum, fungsi harus vektorisasi - jika ini adalah fungsi yang aneh, Anda dapat menulis wacky.function <- function(col.1, col.2){...}, lalu iris.wacky <- wacky.function(iris$Sepal.Length, iris$Petal.Length).
colcarroll
Seringkali mereka harus saya tebak, tetapi saya pikir ketika Anda menggunakan sesuatu seperti dplyratau plyratau mengatakan data.tableAnda harus mencoba menggunakan idiom mereka sehingga kode Anda tidak menjadi campuran gaya yang sulit untuk dibagikan. Karena itu pertanyaannya.
Stephen Henderson
Baris pertama plyrdokumentasi adalah "plyr adalah seperangkat alat yang memecahkan serangkaian masalah umum: Anda perlu memecah masalah besar menjadi bagian-bagian yang dapat dikelola, mengoperasikan setiap bagian dan kemudian menyatukan semua bagian." Ini sepertinya masalah yang sangat berbeda di mana operasi kolom dasar merupakan alat terbaik. Ini juga mungkin menjelaskan mengapa tidak ada perintah plyr/ "alami" dplyruntuk melakukan ini.
colcarroll
5
Untuk memotong kutipan terkenal: " Jika yang Anda miliki hanyalah plyr, Anda akan menggunakannya untuk palu dan obeng juga "
thelatemail