ทำไมต้องใช้ purrr :: map แทนที่จะเป็น lapply


172

มีเหตุผลใดบ้างที่ฉันควรใช้

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

แทน

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

ผลลัพธ์ควรเหมือนกันและมาตรฐานที่ฉันทำดูเหมือนจะแสดงว่าlapplyเร็วกว่าเล็กน้อย (ควรเป็นmapความต้องการในการประเมินอินพุตที่ไม่ได้มาตรฐานการประเมินทั้งหมด)

ดังนั้นจะมีเหตุผลว่าทำไมสำหรับกรณีที่เรียบง่ายเช่นที่จริงผมควรพิจารณาเปลี่ยนpurrr::map? ผมไม่ได้ถามเกี่ยวกับการชอบที่นี่หนึ่งหรือไม่ชอบเกี่ยวกับไวยากรณ์ฟังก์ชันอื่น ๆ โดย purrr ฯลฯ แต่อย่างเคร่งครัดเกี่ยวกับการเปรียบเทียบpurrr::mapกับสมมติว่าใช้การประเมินผลมาตรฐานคือlapply map(<list-like-object>, function(x) <do stuff>)มีข้อได้เปรียบอะไรบ้างที่purrr::mapมีในด้านประสิทธิภาพการจัดการข้อยกเว้น ฯลฯ ? ความคิดเห็นด้านล่างแนะนำว่าไม่เป็นเช่นนั้น แต่อาจมีบางคนที่สามารถอธิบายรายละเอียดเพิ่มเติมได้อีกเล็กน้อย?


8
สำหรับกรณีที่ใช้งานง่ายแน่นอนควรใช้ฐาน R และหลีกเลี่ยงการพึ่งพา หากคุณโหลดtidyverseแม้ว่าคุณอาจได้รับประโยชน์จากท่อ%>%และฟังก์ชั่นที่ไม่ระบุชื่อ~ .x + 1ไวยากรณ์
Aurèle

49
นี่เป็นคำถามของสไตล์ที่ค่อนข้างสวย คุณควรรู้ว่าฟังก์ชัน R พื้นฐานทำอะไรได้บ้างเพราะทุกสิ่งที่เป็นระเบียบเรียบร้อยนี้เป็นเพียงเปลือกหอยที่อยู่ด้านบน ณ จุดหนึ่งเปลือกนั้นจะแตก
Hong Ooi

9
~{}แลมบ์ดาลัด (มีหรือไม่มี{}ซีลข้อตกลงสำหรับฉันธรรมดาpurrr::map(). ประเภทการบังคับใช้ของpurrr::map_…()ที่มีประโยชน์และป้านน้อยกว่าvapply(). purrr::map_df()เป็นฟังก์ชั่นสุดแพง แต่ก็ยังช่วยลดความยุ่งยากรหัส. มีอะไรผิดพลาดกับการผสานกับฐาน R [lsv]apply()แม้ว่า .
hrbrmstr

4
ขอบคุณสำหรับคำถาม - ประเภทของสิ่งที่ฉันยังดู ฉันใช้ R มานานกว่า 10 ปีและไม่ใช้และไม่ใช้purrrของ จุดของฉันต่อไปนี้: tidyverseเป็นนิยายสำหรับการวิเคราะห์ / การโต้ตอบ / รายงานสิ่งที่ไม่ได้สำหรับการเขียนโปรแกรม หากคุณต้องการใช้งานlapplyหรือmapคุณกำลังเขียนโปรแกรมอยู่และอาจจบลงด้วยการสร้างแพ็คเกจหนึ่งวัน จากนั้นยิ่งพึ่งพาน้อยที่สุด บวก: บางครั้งฉันเห็นคนใช้mapด้วยไวยากรณ์ค่อนข้างชัดเจนหลังจาก และตอนนี้ฉันเห็นการทดสอบการแสดง: ถ้าคุณคุ้นเคยกับapplyครอบครัว: ทำตาม
Eric Lecoutre

4
Tim คุณเขียนว่า: "ฉันไม่ได้ถามที่นี่เกี่ยวกับคนที่ชอบหรือไม่ชอบเกี่ยวกับไวยากรณ์, ฟังก์ชั่นอื่น ๆ ที่จัดทำโดย purrr ฯลฯ แต่อย่างเคร่งครัดเกี่ยวกับการเปรียบเทียบของ purrr :: แผนที่กับ lapply สมมติว่าใช้การประเมินมาตรฐาน" และคำตอบที่คุณยอมรับคือ คนที่ผ่านสิ่งที่คุณพูดคุณไม่ต้องการให้คนอื่นไป
Carlos Cinelli

คำตอบ:


232

หากฟังก์ชั่นเดียวที่คุณใช้จาก purrr คือใช่map()ไม่ใช่ข้อดีนั้นไม่สำคัญ ในฐานะที่เป็น Rich Pauloo ชี้ให้เห็นข้อดีหลักของการmap()เป็นผู้ช่วยเหลือที่ช่วยให้คุณเขียนรหัสขนาดกะทัดรัดสำหรับกรณีพิเศษทั่วไป:

  • ~ . + 1 เทียบเท่ากับ function(x) x + 1

  • list("x", 1)function(x) x[["x"]][[1]]เทียบเท่ากับ ผู้ช่วยเหลือเหล่านี้ดูกว้างกว่า[[ดู?pluckรายละเอียดเล็กน้อย สำหรับrectangling ข้อมูลที่ .defaultอาร์กิวเมนต์เป็นประโยชน์อย่างยิ่ง

แต่ส่วนใหญ่เวลาที่คุณไม่ได้ใช้งาน*apply()/ map() ฟังก์ชั่นเดียวคุณกำลังใช้งานมันหลาย ๆ อันและประโยชน์ของ purrr นั้นมีความสอดคล้องกันอย่างมากระหว่างฟังก์ชัน ตัวอย่างเช่น:

  • อาร์กิวเมนต์แรกlapply()คือข้อมูล อาร์กิวเมนต์แรกที่ mapply()เป็นฟังก์ชัน อาร์กิวเมนต์แรกของฟังก์ชันแผนที่ทั้งหมดจะเป็นข้อมูลเสมอ

  • ด้วยvapply(), sapply()และmapply()คุณสามารถเลือกที่จะไม่แสดงชื่อในเอาต์พุตด้วยUSE.NAMES = FALSE; แต่ lapply()ไม่มีข้อโต้แย้งนั้น

  • ไม่มีวิธีที่สอดคล้องกันเพื่อส่งผ่านอาร์กิวเมนต์ที่สอดคล้องกันไปยังฟังก์ชัน mapper ฟังก์ชั่นส่วนใหญ่ใช้...แต่mapply()ใช้ MoreArgs(ซึ่งคุณคาดหวังที่จะเรียกว่าMORE.ARGS) และ Map(), Filter()และReduce()คาดหวังให้คุณสร้างฟังก์ชั่นใหม่ที่ไม่ระบุชื่อ ในฟังก์ชั่นแผนที่อาร์กิวเมนต์คงที่มักจะตามหลังชื่อฟังก์ชั่น

  • ฟังก์ชัน purrr เกือบทุกชนิดนั้นมีความเสถียร: คุณสามารถทำนายชนิดเอาต์พุตเฉพาะจากชื่อฟังก์ชัน นี้ไม่เป็นความจริงสำหรับ หรือsapply() mapply()ใช่นั่นคือvapply(); mapply()แต่มีไม่เทียบเท่า

คุณอาจคิดว่าความแตกต่างเล็กน้อยเหล่านี้ไม่สำคัญ (เช่นเดียวกับบางคนคิดว่าไม่มีประโยชน์ในการ stringr เหนือการแสดงออกปกติ R ฐาน) แต่จากประสบการณ์ของฉันพวกเขาทำให้เกิดแรงเสียดทานที่ไม่จำเป็นเมื่อเขียนโปรแกรม (คำสั่งอาร์กิวเมนต์ที่แตกต่าง ฉัน) และพวกเขาทำให้เทคนิคการเขียนโปรแกรมใช้งานยากที่จะเรียนรู้เพราะเช่นเดียวกับความคิดที่ยิ่งใหญ่คุณต้องเรียนรู้รายละเอียดเล็ก ๆ น้อย ๆ ด้วย

Purrr ยังเติมเต็มในแผนที่ที่หลากหลายซึ่งขาดหายไปจากฐาน R:

  • modify()รักษาประเภทของข้อมูลที่ใช้[[<-ในการแก้ไข "ในสถานที่" เมื่อใช้ร่วมกับ_ifชุดตัวแปรนี้อนุญาตให้ใช้รหัส (IMO สวยงาม) เช่นmodify_if(df, is.factor, as.character)

  • map2()ช่วยให้คุณสามารถ map พร้อมกันมากกว่าและx yสิ่งนี้ทำให้ง่ายต่อการแสดงความคิดเห็นเช่น map2(models, datasets, predict)

  • imap()ช่วยให้คุณสามารถแมปพร้อมกันxและดัชนีของมัน (ชื่อหรือตำแหน่ง) สิ่งนี้ทำให้ง่ายต่อการ (เช่น) โหลดcsvไฟล์ทั้งหมด ในไดเรกทอรีเพิ่มfilenameคอลัมน์ในแต่ละไฟล์

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
  • walk()ส่งคืนอินพุตที่ล่องหน และมีประโยชน์เมื่อคุณเรียกใช้ฟังก์ชันสำหรับผลข้างเคียง (เช่นการเขียนไฟล์ลงดิสก์)

ไม่ต้องพูดถึงผู้ช่วยเหลืออื่น ๆ เช่นและsafely()partial()

โดยส่วนตัวแล้วฉันพบว่าเมื่อฉันใช้ purrr ฉันสามารถเขียนรหัสการทำงานที่มีแรงเสียดทานน้อยลงและง่ายขึ้น มันลดช่องว่างระหว่างการคิดความคิดและนำไปใช้ แต่ระยะทางของคุณอาจแตกต่างกันไป ไม่จำเป็นต้องใช้ purrr เว้นแต่จะช่วยคุณได้จริง

Microbenchmarks

ใช่จะช้ากว่าเล็กน้อยmap() lapply()แต่ค่าใช้จ่ายในการใช้ map()หรือlapply()ขับเคลื่อนด้วยสิ่งที่คุณทำแผนที่ไม่ใช่ค่าใช้จ่ายในการทำลูป microbenchmark ด้านล่างแสดงให้เห็นว่าค่าใช้จ่ายในการmap()เปรียบเทียบกับlapply()ประมาณ 40 ns ต่อองค์ประกอบซึ่งดูเหมือนว่าไม่น่าจะส่งผลกระทบต่อรหัส 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
คุณหมายถึงใช้การแปลง () ในตัวอย่างนั้นหรือไม่? เช่นเดียวกับใน base R transform () หรือฉันขาดอะไรไป? transform () ให้ชื่อไฟล์เป็นปัจจัยหนึ่งซึ่งจะสร้างคำเตือนเมื่อคุณ (ตามธรรมชาติ) ต้องการผูกแถวเข้าด้วยกัน mutate () ให้คอลัมน์ตัวละครของชื่อไฟล์ที่ฉันต้องการ มีเหตุผลที่จะไม่ใช้ที่นั่นหรือไม่?
doctorG

2
ใช่ใช้งานง่ายmutate()กว่าฉันแค่ต้องการตัวอย่างง่ายๆ
hadley

ไม่ควรระบุลักษณะเฉพาะบางอย่างในคำตอบนี้ map_*เป็นสิ่งที่ทำให้ฉันโหลดpurrrในสคริปต์มากมาย มันช่วยให้ฉันมี 'การควบคุมโฟลว์' ของโค้ดของฉัน ( stopifnot(is.data.frame(x)))
คุณพ่อ

2
ggplot และ data.table นั้นยอดเยี่ยม แต่เราต้องการแพ็คเกจใหม่สำหรับทุกฟังก์ชั่นใน R หรือไม่?
adn bps

58

เปรียบเทียบpurrrและlapplyเดือดลงไปอำนวยความสะดวกและความเร็ว


1. purrr::mapสะดวกกว่าการใช้ syntactically

แยกองค์ประกอบที่สองของรายการ

map(list, 2)  

ซึ่งเป็น @F Privéชี้ให้เห็นเช่นเดียวกับ:

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

กับ lapply

lapply(list, 2) # doesn't work

เราจำเป็นต้องผ่านฟังก์ชั่นที่ไม่ระบุชื่อ ...

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

... หรือ @RichScriven ชี้ให้เห็นเราผ่าน[[การโต้เถียงlapply

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

ดังนั้นหากพบว่าตัวเองใช้ฟังก์ชั่นการใช้หลายรายการlapplyและยางทั้งกำหนดฟังก์ชั่นที่กำหนดเองหรือการเขียนฟังก์ชั่นที่ไม่ระบุชื่อ, purrrความสะดวกสบายเป็นหนึ่งในเหตุผลที่จะโปรดปราน

2. ฟังก์ชั่นแผนที่เฉพาะประเภทเพียงหลายบรรทัดของรหัส

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

แต่ละเหล่านี้ประเภทเฉพาะฟังก์ชั่นแผนที่จะส่งกลับเวกเตอร์มากกว่ารายการที่ส่งกลับโดยและmap() lapply()หากคุณกำลังจัดการกับรายการเวกเตอร์ที่ซ้อนกันคุณสามารถใช้ฟังก์ชั่นแผนที่เฉพาะประเภทเหล่านี้เพื่อดึงเวกเตอร์โดยตรงและรวมเวกเตอร์เข้าด้วยกันโดยตรงใน int, dbl และ chr เวกเตอร์ รุ่นฐาน R จะมีลักษณะคล้ายas.numeric(sapply(...)), as.character(sapply(...))ฯลฯ

map_<type>ฟังก์ชั่นยังมีคุณภาพที่มีประโยชน์ว่าหากพวกเขาไม่สามารถกลับเวกเตอร์อะตอมของชนิดที่ระบุพวกเขาล้มเหลว สิ่งนี้มีประโยชน์เมื่อกำหนดโฟลว์ควบคุมที่เข้มงวดซึ่งคุณต้องการให้ฟังก์ชั่นล้มเหลวถ้ามัน [อย่างใด] สร้างประเภทวัตถุที่ไม่ถูกต้อง

3. ความสะดวกสบายนอกเหนือจากlapply[เล็กน้อย] เร็วกว่าmap

การใช้purrrฟังก์ชั่นอำนวยความสะดวกเช่น @F Privéชี้ให้เห็นว่าการประมวลผลช้าลงเล็กน้อย เรามาแข่งกัน 4 เรื่องที่ผมนำเสนอข้างต้นกัน

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

ป้อนคำอธิบายรูปภาพที่นี่

และผู้ชนะคือ....

lapply(list, `[[`, 2)

กล่าวโดยสรุปหากความเร็วbase::lapplyที่แท้จริงคือสิ่งที่คุณตามมา: (แม้ว่าจะไม่เร็วกว่านั้นมาก)

สำหรับไวยากรณ์และการแสดงผลอย่างง่าย: purrr::map


purrrบทแนะนำที่ยอดเยี่ยมนี้เน้นความสะดวกสบายของการไม่ต้องเขียนฟังก์ชั่นนิรนามเมื่อใช้purrrงานและประโยชน์ของmapฟังก์ชั่นเฉพาะประเภท


2
โปรดทราบว่าหากคุณใช้function(x) x[[2]]แทนเพียงแค่2ก็จะช้าลงน้อย เวลาพิเศษทั้งหมดนี้เกิดจากการตรวจสอบที่lapplyไม่ได้ทำ
F. Privé

17
คุณไม่จำเป็นต้องใช้ฟังก์ชั่นนิรนาม [[เป็นฟังก์ชั่น lapply(list, "[[", 3)คุณสามารถทำได้
Rich Scriven

@ RichScriven ที่เหมาะสม ซึ่งจะทำให้ไวยากรณ์สำหรับการใช้ lapply ผ่าน purrr ง่ายขึ้น
คนรวย Pauloo

37

ถ้าเราไม่ได้พิจารณาประเด็นของการลิ้มรส (มิฉะนั้นคำถามนี้ควรจะปิด) หรือสอดคล้องไวยากรณ์สไตล์ ฯลฯ คำตอบคือไม่มีไม่มีเหตุผลพิเศษที่จะใช้mapแทนlapplyหรือรูปแบบอื่น ๆ vapplyของนำไปใช้ในครอบครัวเช่นเข้มงวด

PS: สำหรับคนที่ downvoting อย่างไร้เหตุผลเพียงจำ OP เขียนว่า:

ฉันไม่ได้ถามที่นี่เกี่ยวกับคนที่ชอบหรือไม่ชอบเกี่ยวกับไวยากรณ์, ฟังก์ชั่นอื่น ๆ ที่จัดทำโดย purrr ฯลฯ แต่อย่างเคร่งครัดเกี่ยวกับการเปรียบเทียบของแผนที่ purrr :: map กับ lapply สมมติว่าใช้การประเมินมาตรฐาน

หากคุณไม่ได้พิจารณาไวยากรณ์หรือฟังก์ชันอื่น ๆไม่มีเหตุผลเป็นพิเศษเพื่อการใช้งานpurrr mapฉันใช้purrrตัวเองและฉันก็พอใจกับคำตอบของ Hadley แต่มันกลับกลายเป็นสิ่งที่ OP อธิบายอย่างตรงไปตรงมาเขาไม่ได้ถาม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.