Mengapa menggunakan purrr :: map bukan lapply?


171

Apakah ada alasan mengapa saya harus menggunakannya

map(<list-like-object>, function(x) <do stuff>)

dari pada

lapply(<list-like-object>, function(x) <do stuff>)

outputnya harus sama dan tolok ukur yang saya buat nampaknya menunjukkan bahwa lapplysedikit lebih cepat (harus sesuai dengan mapkebutuhan untuk mengevaluasi semua input non-standar-evaluasi).

Jadi apakah ada alasan mengapa untuk kasus sederhana seperti itu saya benar-benar harus mempertimbangkan untuk beralih ke purrr::map? Saya tidak bertanya di sini tentang suka atau tidak suka seseorang tentang sintaks, fungsi lain yang disediakan oleh purrr dll, tetapi hanya tentang perbandingan purrr::mapdengan lapplyasumsi menggunakan evaluasi standar, yaitu map(<list-like-object>, function(x) <do stuff>). Apakah ada kelebihan yang purrr::mapdimiliki dalam hal kinerja, penanganan pengecualian, dll.? Komentar di bawah ini menunjukkan bahwa tidak, tetapi mungkin seseorang dapat menguraikan sedikit lebih banyak?


8
Untuk kasus penggunaan yang sederhana memang, lebih baik tetap dengan basis R dan menghindari ketergantungan. Jika Anda sudah memuatnya tidyverse, Anda mungkin mendapat manfaat dari sintaksis %>%fungsi pipa dan anonim~ .x + 1
Auréle

49
Ini adalah masalah gaya. Anda harus tahu apa fungsi dasar R lakukan, karena semua barang rapi ini hanya shell di atasnya. Pada titik tertentu, cangkang itu akan pecah.
Hong Ooi

9
~{}pintas lambda (dengan atau tanpa {}segel kesepakatan bagi saya untuk polos purrr::map(). Jenis-penegakan purrr::map_…()yang berguna dan kurang tumpul dari vapply(). purrr::map_df()adalah fungsi super mahal tetapi juga menyederhanakan kode. Sama sekali tidak ada yang salah dengan bertahan dengan basis R [lsv]apply(), meskipun .
hrbrmstr

4
Terima kasih atas pertanyaan - jenis barang yang juga saya lihat. Saya menggunakan R sejak lebih dari 10 tahun dan secara pasti tidak dan tidak akan menggunakan purrrbarang. Maksud saya adalah sebagai berikut: tidyverseluar biasa untuk analisis / interaktif / melaporkan barang, bukan untuk pemrograman. Jika Anda harus menggunakan lapplyatau mapkemudian Anda sedang pemrograman dan mungkin berakhir suatu hari dengan membuat paket. Maka semakin sedikit ketergantungan yang terbaik. Plus: Saya kadang-kadang melihat orang menggunakan mapsintaks yang tidak jelas setelahnya. Dan sekarang saya melihat pengujian kinerja: jika Anda terbiasa dengan applykeluarga: patuhi itu.
Eric Lecoutre

4
Tim yang Anda tulis: "Saya tidak bertanya di sini tentang suka atau tidak suka seseorang tentang sintaks, fungsi lain yang disediakan oleh purrr dll., Tetapi secara ketat tentang perbandingan purrr: peta dengan sedikit asumsi dengan menggunakan evaluasi standar" dan jawaban yang Anda terima adalah yang sesuai dengan apa yang Anda katakan Anda tidak ingin orang lain pergi.
Carlos Cinelli

Jawaban:


232

Jika satu-satunya fungsi yang Anda gunakan dari purr adalah map(), maka tidak, keuntungannya tidak besar. Seperti yang ditunjukkan oleh Rich Pauloo, keuntungan utama map()adalah bantuan yang memungkinkan Anda menulis kode ringkas untuk kasus khusus umum:

  • ~ . + 1 setara dengan function(x) x + 1

  • list("x", 1)setara dengan function(x) x[["x"]][[1]]. Pembantu ini sedikit lebih umum daripada [[- lihat ?pluckdetailnya. Untuk persegi panjang data , .defaultargumen ini sangat membantu.

Tetapi sebagian besar waktu Anda tidak menggunakan satu *apply()/ map() fungsi, Anda menggunakan banyak dari mereka, dan keuntungan dari purrr adalah konsistensi yang jauh lebih besar antara fungsi. Sebagai contoh:

  • Argumen pertama lapply()adalah data; argumen pertama mapply()adalah fungsi. Argumen pertama untuk semua fungsi peta selalu data.

  • Dengan vapply(),, sapply()dan mapply()Anda dapat memilih untuk menekan nama pada output dengan USE.NAMES = FALSE; tetapi lapply()tidak memiliki argumen itu.

  • Tidak ada cara yang konsisten untuk meneruskan argumen yang konsisten ke fungsi mapper. Sebagian besar fungsi menggunakan ...tetapi mapply()menggunakan MoreArgs(yang Anda harapkan dipanggil MORE.ARGS), dan Map(), Filter()dan Reduce()berharap Anda membuat fungsi anonim baru. Dalam fungsi peta, argumen konstan selalu muncul setelah nama fungsi.

  • Hampir setiap fungsi purrr adalah tipe stabil: Anda dapat memprediksi tipe output secara eksklusif dari nama fungsi. Ini tidak benar untuk sapply()atau mapply(). Ya ada vapply(); tapi tidak ada padanan untuk mapply().

Anda mungkin berpikir bahwa semua perbedaan kecil ini tidak penting (seperti beberapa orang berpikir bahwa tidak ada keuntungan untuk merangkai ekspresi reguler basis R), tetapi dalam pengalaman saya mereka menyebabkan gesekan yang tidak perlu saat pemrograman (perintah argumen yang berbeda selalu digunakan untuk trip saya), dan mereka membuat teknik pemrograman fungsional lebih sulit untuk dipelajari karena juga ide-ide besar, Anda juga harus belajar banyak detail insidental.

Purrr juga mengisi beberapa varian peta praktis yang tidak ada di basis R:

  • modify()mempertahankan tipe data yang digunakan [[<-untuk memodifikasi "di tempat". Sehubungan dengan _ifvarian ini memungkinkan untuk kode (IMO indah) sepertimodify_if(df, is.factor, as.character)

  • map2()memungkinkan Anda untuk memetakan sekaligus xdan y. Ini membuatnya lebih mudah untuk mengekspresikan ide-ide seperti map2(models, datasets, predict)

  • imap()memungkinkan Anda untuk memetakan secara bersamaan xdan indeksnya (baik nama atau posisi). Ini memudahkan (misalnya) memuat semua csvfile dalam direktori, menambahkan filenamekolom ke masing-masing file .

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
  • walk()mengembalikan inputnya tanpa terlihat; dan berguna ketika Anda memanggil fungsi untuk efek sampingnya (yaitu menulis file ke disk).

Belum lagi pembantu lainnya seperti safely()dan partial().

Secara pribadi, saya menemukan bahwa ketika saya menggunakan purrr, saya dapat menulis kode fungsional dengan lebih sedikit gesekan dan kemudahan yang lebih besar; itu mengurangi kesenjangan antara memikirkan ide dan mengimplementasikannya. Tetapi jarak tempuh Anda mungkin berbeda; tidak perlu menggunakan purrr kecuali itu benar-benar membantu Anda.

Microbenchmark

Ya, map()sedikit lebih lambat dari lapply(). Tetapi biaya menggunakan map()atau lapply()didorong oleh apa yang Anda pemetaan, bukan biaya overhead untuk melakukan loop. Microbenchmark di bawah ini menunjukkan bahwa biaya map()dibandingkan dengan lapply()sekitar 40 ns per elemen, yang tampaknya tidak berdampak material terhadap sebagian besar kode R.

library(purrr)
n <- 1e4
x <- 1:n
f <- function(x) NULL

mb <- microbenchmark::microbenchmark(
  lapply = lapply(x, f),
  map = map(x, f)
)
summary(mb, unit = "ns")$median / n
#> [1] 490.343 546.880

2
Apakah Anda bermaksud menggunakan transform () dalam contoh itu? Seperti pada base R transform (), atau apakah saya melewatkan sesuatu? transform () memberi Anda nama file sebagai faktor, yang menghasilkan peringatan ketika Anda (secara alami) ingin mengikat baris bersama. mutate () memberi saya kolom karakter dari nama file yang saya inginkan. Apakah ada alasan untuk tidak menggunakannya di sana?
doctorG

2
Ya, lebih baik digunakan mutate(), saya hanya ingin contoh sederhana tanpa deps lain.
hadley

Tidakkah tipe-kekhususan muncul di suatu tempat dalam jawaban ini? map_*adalah apa yang membuat saya memuat purrrbanyak skrip. Ini membantu saya dengan beberapa aspek 'aliran kendali' dari kode saya ( stopifnot(is.data.frame(x))).
Fr.

2
ggplot dan data.table memang bagus, tetapi apakah kita benar-benar membutuhkan paket baru untuk setiap fungsi tunggal di R?
dan bps

58

Membandingkan purrrdan lapplybermuara pada kenyamanan dan kecepatan .


1. purrr::mapsecara sintaksis lebih nyaman daripada lapply

ekstrak elemen kedua dari daftar

map(list, 2)  

yang sebagai @F. Privé tunjukkan, sama dengan:

map(list, function(x) x[[2]])

dengan lapply

lapply(list, 2) # doesn't work

kita harus melewati fungsi anonim ...

lapply(list, function(x) x[[2]])  # now it works

... atau seperti yang ditunjukkan @RichScriven, kami memberikan [[argumenlapply

lapply(list, `[[`, 2)  # a bit more simple syntantically

Jadi jika menemukan diri Anda menerapkan fungsi ke banyak daftar menggunakan lapply, dan bosan mendefinisikan fungsi kustom atau menulis fungsi anonim, kenyamanan adalah salah satu alasan untuk memilih purrr.

2. Jenis-fungsi peta spesifik hanya banyak baris kode

  • map_chr()
  • map_lgl()
  • map_int()
  • map_dbl()
  • map_df()

Masing-masing fungsi peta tipe spesifik ini mengembalikan vektor, bukan daftar yang dikembalikan oleh map()dan lapply(). Jika Anda berurusan dengan daftar vektor bersarang, Anda dapat menggunakan fungsi peta khusus ini untuk menarik vektor secara langsung, dan memaksa vektor langsung ke vektor int, dbl, chr. Versi dasar R akan terlihat seperti as.numeric(sapply(...)), as.character(sapply(...)), dll

map_<type>Fungsi - fungsinya juga memiliki kualitas yang berguna bahwa jika mereka tidak dapat mengembalikan vektor atom dari tipe yang ditunjukkan, mereka gagal. Ini berguna ketika mendefinisikan aliran kontrol yang ketat, di mana Anda ingin fungsi gagal jika [entah bagaimana] menghasilkan tipe objek yang salah.

3. Kesamping kenyamanan, lapplyadalah [sedikit] lebih cepat daripadamap

Menggunakan purrrfungsi kenyamanan, sebagai @F. Privé menunjukkan memperlambat pemrosesan sedikit. Mari kita berlomba masing-masing dari 4 kasus yang saya disajikan di atas

# devtools::install_github("jennybc/repurrrsive")
library(repurrrsive)
library(purrr)
library(microbenchmark)
library(ggplot2)

mbm <- microbenchmark(
lapply       = lapply(got_chars[1:4], function(x) x[[2]]),
lapply_2     = lapply(got_chars[1:4], `[[`, 2),
map_shortcut = map(got_chars[1:4], 2),
map          = map(got_chars[1:4], function(x) x[[2]]),
times        = 100
)
autoplot(mbm)

masukkan deskripsi gambar di sini

Dan pemenangnya adalah....

lapply(list, `[[`, 2)

Singkatnya, jika kecepatan mentah adalah yang Anda cari: base::lapply(meskipun itu tidak jauh lebih cepat)

Untuk sintaks dan ekspresi yang sederhana: purrr::map


purrrTutorial yang sangat baik ini menyoroti kenyamanan tidak harus secara eksplisit menulis fungsi anonim saat menggunakan purrr, dan manfaat dari mapfungsi tipe-spesifik .


2
Perhatikan bahwa jika Anda menggunakan function(x) x[[2]]bukan hanya 2, itu akan kurang lambat. Semua waktu tambahan ini disebabkan oleh cek yang lapplytidak berfungsi.
F. Privé

17
Anda tidak "perlu" fungsi anonim. [[adalah suatu fungsi. Anda bisa melakukannya lapply(list, "[[", 3).
Rich Scriven

@RichScriven itu masuk akal. Itu tidak menyederhanakan sintaks untuk menggunakan lapply over purrr.
Rich Pauloo

37

Jika kita tidak mempertimbangkan aspek selera (kalau tidak pertanyaan ini harus ditutup) atau konsistensi sintaksis, gaya dll. Jawabannya tidak, tidak ada alasan khusus untuk digunakan mapsebagai ganti lapplyatau varian lain dari keluarga yang berlaku, seperti yang lebih ketat vapply.

PS: Kepada orang-orang itu dengan sembrono downvoting, ingat saja OP menulis:

Saya tidak bertanya di sini tentang suka atau tidak suka seseorang tentang sintaks, fungsi-fungsi lain yang disediakan oleh purrr dll, tetapi hanya tentang perbandingan purrr :: map dengan sedikit asumsi dengan menggunakan evaluasi standar

Jika Anda tidak mempertimbangkan sintaks atau fungsi lain dari purrr, tidak ada alasan khusus untuk digunakan map. Saya menggunakan purrrdiri saya sendiri dan saya baik-baik saja dengan jawaban Hadley, tetapi ironisnya hal itu lebih penting daripada yang dikatakan OP di muka bahwa ia tidak bertanya.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.