Tại sao sử dụng purrr :: map thay vì lapply?


171

Có bất kỳ lý do tại sao tôi nên sử dụng

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

thay vì

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

đầu ra phải giống nhau và các điểm chuẩn tôi thực hiện dường như hiển thị lapplynhanh hơn một chút (nó phải nhưmap cần phải đánh giá tất cả các đầu vào không đánh giá tiêu chuẩn).

Vì vậy, có lý do tại sao cho các trường hợp đơn giản như vậy tôi thực sự nên xem xét chuyển sang purrr::map? Tôi không hỏi ở đây về việc thích hay không thích của ai đó về cú pháp, các chức năng khác được cung cấp bởi purrr, v.v., nhưng nghiêm túc về việc so sánh purrr::mapvới lapplygiả định sử dụng đánh giá tiêu chuẩn, tức là map(<list-like-object>, function(x) <do stuff>). Có bất kỳ lợi thế nào purrr::mapvề hiệu suất, xử lý ngoại lệ, vv? Các ý kiến ​​dưới đây cho thấy rằng nó không, nhưng có lẽ ai đó có thể giải thích thêm một chút?


8
Đối với các trường hợp sử dụng đơn giản thực sự, tốt hơn nên gắn bó với cơ sở R và tránh phụ thuộc. Nếu bạn đã tải tidyversemặc dù, bạn có thể được hưởng lợi từ cú pháp %>%hàm và ẩn danh~ .x + 1
Aurèle

49
Đây là khá nhiều câu hỏi về phong cách. Bạn nên biết các hàm cơ sở R làm gì, bởi vì tất cả những thứ gọn gàng này chỉ là một cái vỏ trên nó. Đến một lúc nào đó, cái vỏ đó sẽ vỡ.
Hồng Ooi

9
~{}lambda phím tắt (có hoặc không có {}con dấu thỏa thuận đối với tôi cho đồng bằng purrr::map(). Các loại thực thi của purrr::map_…()rất tiện dụng và ít tù hơn vapply(). purrr::map_df()là một chức năng siêu đắt nhưng nó cũng đơn giản hoá mã. Có hoàn toàn không có gì sai với gắn bó với cơ sở R [lsv]apply(), mặc dù .
hrbrmstr

4
Cảm ơn bạn cho câu hỏi - loại công cụ tôi cũng đã xem. Tôi đang sử dụng R từ hơn 10 năm nay và chắc chắn không sử dụng và không sử dụng purrrcông cụ. Quan điểm của tôi là: tidyversetuyệt vời cho các công cụ phân tích / tương tác / báo cáo, không phải cho lập trình. Nếu bạn đang phải sử dụng lapplyhoặc mapsau đó bạn đang lập trình và có thể kết thúc một ngày với việc tạo một gói. Sau đó, càng ít phụ thuộc tốt nhất. Thêm vào đó, đôi khi tôi thấy mọi người sử dụng mapvới cú pháp khá tối nghĩa sau đó. Và bây giờ tôi thấy thử nghiệm biểu diễn: nếu bạn đã quen với applygia đình: hãy bám lấy nó.
Eric Lecoutre

4
Tim bạn đã viết: "Tôi không hỏi ở đây về việc thích hay không thích về cú pháp, các chức năng khác được cung cấp bởi purrr, v.v., nhưng nghiêm túc về việc so sánh purrr :: map với giả định lapply bằng cách sử dụng đánh giá tiêu chuẩn" và câu trả lời bạn chấp nhận là một trong những điều đi qua chính xác những gì bạn nói bạn không muốn mọi người đi qua.
Carlos Cinelli

Câu trả lời:


232

Nếu chức năng duy nhất bạn đang sử dụng từ purrr là map()không, thì những lợi thế không đáng kể. Như Rich Pauloo chỉ ra, ưu điểm chính của map()là những người trợ giúp cho phép bạn viết mã nhỏ gọn cho các trường hợp đặc biệt phổ biến:

  • ~ . + 1 tương đương với function(x) x + 1

  • list("x", 1)tương đương với function(x) x[["x"]][[1]]. Những người trợ giúp này có một chút tổng quát hơn [[- xem ?pluckđể biết chi tiết. Đối với hình chữ nhật dữ liệu , .defaultđối số là đặc biệt hữu ích.

Nhưng hầu hết thời gian bạn không sử dụng một chức năng *apply()/ duy nhất map(), bạn đang sử dụng một loạt chúng và lợi thế của purrr là tính nhất quán lớn hơn nhiều giữa các chức năng. Ví dụ:

  • Đối số đầu tiên lapply()là dữ liệu; đối số đầu tiên mapply()là hàm. Đối số đầu tiên cho tất cả các chức năng bản đồ luôn là dữ liệu.

  • Với vapply(), sapply()mapply()bạn có thể chọn chặn tên trên đầu ra bằng USE.NAMES = FALSE; nhưng lapply()không có lập luận đó.

  • Không có cách nhất quán để truyền các đối số nhất quán cho chức năng ánh xạ. Hầu hết các hàm sử dụng ...nhưng mapply()sử dụng MoreArgs(mà bạn muốn được gọi MORE.ARGS) và Map(), Filter()Reduce()mong bạn tạo một hàm ẩn danh mới. Trong các hàm ánh xạ, đối số không đổi luôn xuất hiện sau tên hàm.

  • Hầu như mọi hàm purrr đều ổn định kiểu: bạn có thể dự đoán loại đầu ra độc quyền từ tên hàm. Điều này không đúng với sapply()hoặc mapply(). Vâng, có vapply(); nhưng không có tương đương cho mapply().

Bạn có thể nghĩ rằng tất cả những khác biệt nhỏ này không quan trọng (giống như một số người nghĩ rằng không có lợi thế nào để xâu chuỗi các biểu thức chính quy R), nhưng theo kinh nghiệm của tôi, chúng gây ra ma sát không cần thiết khi lập trình (các lệnh đối số khác nhau luôn được sử dụng để ngắt tôi lên), và chúng làm cho các kỹ thuật lập trình chức năng khó học hơn vì cũng như những ý tưởng lớn, bạn cũng phải học một loạt các chi tiết ngẫu nhiên.

Purrr cũng điền vào một số biến thể bản đồ tiện dụng không có ở cơ sở R:

  • modify()bảo tồn loại dữ liệu bằng cách sử dụng [[<-để sửa đổi "tại chỗ". Kết hợp với _ifbiến thể này cho phép mã (IMO đẹp) nhưmodify_if(df, is.factor, as.character)

  • map2()cho phép bạn lập bản đồ đồng thời trên xy. Điều này giúp dễ dàng thể hiện ý tưởng như map2(models, datasets, predict)

  • imap()cho phép bạn ánh xạ đồng thời trên xvà các chỉ số của nó (tên hoặc vị trí). Điều này giúp dễ dàng (ví dụ) tải tất cả csvcác tệp trong một thư mục, thêm một filenamecột vào mỗi thư mục.

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
  • walk()trả về đầu vào của nó vô hình; và rất hữu ích khi bạn gọi một hàm cho các hiệu ứng phụ của nó (tức là ghi tệp vào đĩa).

Chưa kể những người trợ giúp khác thích safely()partial() .

Cá nhân, tôi thấy rằng khi tôi sử dụng purrr, tôi có thể viết mã chức năng với ít ma sát hơn và dễ dàng hơn; nó làm giảm khoảng cách giữa việc nghĩ ra một ý tưởng và thực hiện nó. Nhưng số dặm của bạn có thể thay đổi; không cần sử dụng purrr trừ khi nó thực sự giúp bạn.

Vi sinh vật

Có, map()là hơi chậm hơn lapply(). Nhưng chi phí sử dụng map()hoặc lapply()được điều khiển bởi những gì bạn lập bản đồ, không phải chi phí thực hiện vòng lặp. Các microbenchmark dưới đây cho thấy rằng chi phí map()so với lapply()khoảng 40 ns cho mỗi phần tử, dường như không có khả năng ảnh hưởng lớn đến hầu hết mã 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
Ý của bạn là sử dụng biến đổi () trong ví dụ đó? Như trong cơ sở R biến đổi (), hoặc tôi đang thiếu một cái gì đó? biến đổi () cung cấp cho bạn tên tệp như là một yếu tố, tạo ra các cảnh báo khi bạn (tự nhiên) muốn liên kết các hàng với nhau. mutate () cung cấp cho tôi cột nhân vật của tên tệp mà tôi muốn. Có một lý do để không sử dụng nó ở đó?
bác sĩG

2
Vâng, tốt hơn để sử dụng mutate(), tôi chỉ muốn một ví dụ đơn giản không có deps khác.
hadley

Không nên đặc trưng loại xuất hiện ở đâu đó trong câu trả lời này? map_*là những gì đã cho tôi tải purrrtrong nhiều kịch bản. Nó đã giúp tôi với một số khía cạnh 'kiểm soát dòng chảy' trong mã của tôi ( stopifnot(is.data.frame(x))).
Cha

2
ggplot và data.table là tuyệt vời, nhưng chúng ta có thực sự cần một gói mới cho mọi chức năng trong R không?
adn bps

58

So sánh purrrlapplysôi sục để thuận tiệntốc độ .


1. purrr::mapvề mặt cú pháp thuận tiện hơn lapply

trích phần tử thứ hai của danh sách

map(list, 2)  

mà như @F. Đặc quyền chỉ ra, giống như:

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

với lapply

lapply(list, 2) # doesn't work

chúng ta cần vượt qua một chức năng ẩn danh ...

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

... hoặc như @RichScriven đã chỉ ra, chúng tôi chuyển [[như một đối số vàolapply

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

Vì vậy, nếu thấy mình áp dụng các chức năng cho nhiều danh sách bằng cách sử dụng lapplyvà sử dụng chức năng tùy chỉnh hoặc viết một chức năng ẩn danh, thì sự tiện lợi là một lý do để ủng hộpurrr .

2. Các hàm bản đồ cụ thể theo kiểu đơn giản là nhiều dòng mã

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

Mỗi hàm bản đồ loại cụ thể này trả về một vectơ, thay vì các danh sách được trả về bởi map()lapply(). Nếu bạn đang xử lý các danh sách các vectơ lồng nhau, bạn có thể sử dụng các hàm bản đồ loại cụ thể này để lấy trực tiếp các vectơ và ép các vectơ trực tiếp vào các vectơ int, dbl, chr. Phiên bản R cơ sở sẽ trông giống như as.numeric(sapply(...)),as.character(sapply(...)) vv

Các map_<type> hàm cũng có chất lượng hữu ích mà nếu chúng không thể trả về một vectơ nguyên tử của loại được chỉ định, chúng sẽ thất bại. Điều này hữu ích khi xác định luồng điều khiển nghiêm ngặt, nơi bạn muốn một hàm bị lỗi nếu nó [bằng cách nào đó] tạo ra loại đối tượng sai.

3. Tiện lợi sang một bên, lapplylà [hơi] nhanh hơnmap

Sử dụng purrrcác chức năng tiện lợi, như @F. Privé chỉ ra làm chậm xử lý một chút. Hãy đua mỗi trong số 4 trường hợp tôi đã trình bày ở trên.

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

nhập mô tả hình ảnh ở đây

Và người chiến thắng là....

lapply(list, `[[`, 2)

Tóm lại, nếu tốc độ thô là những gì bạn đang theo đuổi: base::lapply(mặc dù nó không nhanh hơn nhiều)

Đối với cú pháp đơn giản và biểu cảm: purrr::map


purrrHướng dẫn tuyệt vời này nêu bật sự tiện lợi của việc không phải viết rõ ràng các chức năng ẩn danh khi sử dụng purrrvà lợi ích của các mapchức năng cụ thể theo loại .


2
Lưu ý rằng nếu bạn sử dụng function(x) x[[2]]thay vì chỉ 2, nó sẽ chậm hơn. Tất cả thời gian thêm này là do kiểm tra mà lapplykhông làm.
F. Privé

17
Bạn không "cần" các chức năng ẩn danh. [[là một chức năng. Bạn có thể làm lapply(list, "[[", 3).
Rich Scriven

@RichScriven có ý nghĩa. Điều đó không đơn giản hóa cú pháp để sử dụng lapply trên purrr.
Rich Pauloo

37

Nếu chúng ta không xem xét các khía cạnh của hương vị (nếu không câu hỏi này nên được đóng lại) hoặc tính nhất quán cú pháp, kiểu v.v., câu trả lời là không, không có lý do đặc biệt để sử dụng mapthay vì lapplyhoặc các biến thể khác của gia đình áp dụng, chẳng hạn như nghiêm ngặt hơn vapply.

PS: Gửi tới những người vô cớ, chỉ cần nhớ OP đã viết:

Tôi không hỏi ở đây về việc thích hay không thích của ai đó về cú pháp, các chức năng khác được cung cấp bởi purrr, v.v., nhưng nghiêm túc về việc so sánh purrr :: map với giả định lapply bằng cách sử dụng đánh giá tiêu chuẩn

Nếu bạn không xem xét cú pháp cũng như các chức năng khác của purrr, thì không có lý do đặc biệt nào để sử dụng map. Tôi sử dụng purrrbản thân mình và tôi ổn với câu trả lời của Hadley, nhưng nó trớ trêu thay những điều mà OP tuyên bố trước mà anh ta không hỏi.

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.