programing

왜 lapply 대신 purr::map을 사용합니까?

iphone6s 2023. 7. 1. 08:08
반응형

왜 lapply 대신 purr::map을 사용합니까?

제가 사용해야 할 이유가 있나요?

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

대신에

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

출력은 동일해야 하고 제가 만든 벤치마크는 그것을 보여주는 것 같습니다.lapply조금 더 빠릅니다(그것은 다음과 같아야 합니다).map모든 비표준 평가 입력을 평가해야 합니다.

그래서 그렇게 간단한 경우에 제가 실제로 전환을 고려해야 할 이유가 있나요?purrr::map나는 여기서 구문, purrr 등이 제공하는 다른 기능에 대한 호불호를 묻는 것이 아니라, 엄격하게 비교에 대한 것입니다.purrr::map와 함께lapply표준평사고가다정합다니한용를가▁the▁using▁standard다▁eval,uation니▁assuming가를 사용한다고 가정합니다.map(<list-like-object>, function(x) <do stuff>)어떤 장점이 있습니까?purrr::map성능, 예외 처리 등의 측면에서?아래 의견은 그렇지 않다는 것을 시사하지만, 누군가가 조금 더 자세히 설명할 수 있을까요?

purrr에서 사용하는 유일한 기능이map()그러면 아니요, 이점이 크지 않습니다. 폴루의 은 주적했듯이지요장점은가, ▁as은장점,▁the.map()경우에 할 수 도우미입니다.

  • ~ . + 1는 와동합다니등다에 합니다.function(x) x + 1)\(x) x + 1 R-4.1 버전)

  • list("x", 1)는 와동합다니등다에 합니다.function(x) x[["x"]][[1]]이 도우미들은 보다 좀 더 일반적입니다.[[ 보다?pluck상세한 것은데이터 직사각형의 경우.default인수는 특히 유용합니다.

단의 하만대의경우당단않사다습니지용하하나도신은지부분▁a▁single다▁using않▁but니'습▁not를 사용하지 않습니다.*apply()/map()함수, 당신은 그것들을 많이 사용하고 있고, purrr의 장점은 함수들 사이의 훨씬 더 큰 일관성입니다.예:

  • 번주장째의 첫 번째 lapply()입니다. 첫 는 " 입니인다니입다수첫번째데이터▁is째입니다인수▁to;▁the.mapply()의 첫 입니다.모든 맵 함수의 첫 번째 인수는 항상 데이터입니다.

  • 와 함께vapply(),sapply(),그리고.mapply()을 표시하지 않을 경우 출력에 이름을 할 수 .USE.NAMES = FALSE그러나lapply()그런 주장은 없습니다.

  • 매퍼 함수에 일관된 인수를 전달하는 일관된 방법은 없습니다.는 대분의사용을 합니다....그렇지만mapply()사용하다MoreArgs(당신이 예상할 수 있는 것은)MORE.ARGS ), 및Map(),Filter()그리고.Reduce()새 익명 함수를 만들 것으로 예상됩니다.지도 함수에서 상수 인수는 항상 함수 이름 뒤에 옵니다.

  • 거의 모든 purrr 함수는 유형 안정적입니다. 함수 이름만으로 출력 유형을 예측할 수 있습니다.이는 다음에 해당되지 않습니다.sapply()또는mapply() 네, 있다니가 .vapply()하지만 이에 상응하는 것은 없습니다.mapply().

여러분은 이 모든 사소한 구별들이 중요하지 않다고 생각할지도 모릅니다(일부 사람들이 기본 R 정규식보다 문자열을 더하는 것이 유리하지 않다고 생각하는 것처럼). 하지만 제 경험에 따르면 프로그래밍할 때 불필요한 마찰을 일으킵니다(항상 저를 함정에 빠뜨리는 데 사용되는 다양한 인수 순서).그리고 그들은 기능적인 프로그래밍 기술을 배우기 어렵게 만듭니다. 왜냐하면 큰 아이디어뿐만 아니라 부수적인 세부 사항도 많이 배워야 하기 때문입니다.

또한 Purr은 기본 R에 없는 일부 유용한 지도 변형을 채웁니다.

  • modify()는 다음사용데유보존형다니를 사용하여 을 보존합니다.[[<-"in place"를 수정합니다.와연여하계와 ._if는 (IMO beautiful) 코드를허용다 (IMO beautiful)와 같은 (beautiful) 합니다.modify_if(df, is.factor, as.character)

  • map2()에서는 에위 동에매수있다니습핑할시▁simultaneously▁over에 동시에 매핑할 수 .x그리고.y이것은 다음과 같은 아이디어를 표현하는 것을 더 쉽게 만듭니다.map2(models, datasets, predict)

  • imap()에서는 에위 동에매수있다니습핑할시▁simultaneously▁over에 동시에 매핑할 수 .x및 인덱스(이름 또는 위치).이를 통해 모든 데이터를 쉽게 로드할 수 있습니다(예:csv "" " " "filename각 열에 표시합니다.

    dir("\\.csv$") %>%
      set_names() %>%
      map(read.csv) %>%
      imap(~ transform(.x, filename = .y))
    
  • walk()은 입력을 보이지 않게 반환하며, 부작용(예: 디스크에 파일 쓰기)을 위해 함수를 호출할 때 유용합니다.

다른 조력자들은 말할 것도 없고요safely()그리고.partial().

개인적으로, 저는 퍼럴을 사용할 때, 마찰이 적고 더 쉽게 기능 코드를 작성할 수 있다는 것을 발견했습니다. 아이디어를 생각하는 것과 실행하는 것 사이의 차이를 줄여줍니다.하지만 주행 거리는 다를 수 있습니다. 실제로 도움이 되지 않는 한 퍼런을 사용할 필요가 없습니다.

마이크로벤치마크

네.map() 약느 다니립간보다 약간 .lapply()하지만 사용하는 비용은 비용이 듭니다.map()또는lapply()루프를 수행하는 데 드는 오버헤드가 아니라 매핑하는 내용에 따라 결정됩니다.는 아의미벤비의 비용을 합니다.map()와비여하와 lapply()요소당 약 40ns로, 대부분의 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

purrr그리고.lapply편의성과 속도로 요약할 수 있습니다.


1. purrr::map으로 더 합니다.

목록의 두 번째 요소를 추출합니다.

map(list, 2)  

그것은 @F.프리베는 다음과 같다고 지적했습니다.

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.

유형별 지도 기능은 단순히 코드의 여러 줄 기능을 수행합니다.

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

이러한 각 유형별 지도 함수는 다음과 같이 반환된 목록이 아닌 벡터를 반환합니다.map()그리고.lapply()중첩된 벡터 목록을 처리하는 경우 이러한 유형별 맵 함수를 사용하여 벡터를 직접 추출하고 벡터를 int, dbl, chr 벡터로 직접 강제 적용할 수 있습니다. R 은 기본전다같습다니과음은버와 같은 이 될 입니다.as.numeric(sapply(...)),as.character(sapply(...)) 타기.

map_<type>함수는 또한 지정된 유형의 원자 벡터를 반환할 수 없으면 실패한다는 유용한 특성을 가지고 있습니다.이는 함수가 잘못된 개체 유형을 생성할 경우 실패하도록 하는 엄격한 제어 흐름을 정의할 때 유용합니다.

편리함은 차치하고,lapply[름름]보다map

용사를 합니다.purrr의 편의 기능은 @F. 느려진다고 했습니다.프리베는 처리 속도가 다소 느려진다고 지적했습니다.위에서 제시한 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)

enter image description here

그리고 우승자는..

lapply(list, `[[`, 2)

요약하자면, 원시 속도가 목표라면: (그렇게 빠르지는 않지만)

간단한 구문 및 표현 가능성:


이 우수한 자습서는 사용 시 익명 기능을 명시적으로 쓸 필요가 없다는 편리함을 강조합니다.purrr.map기능들.

은 닫아야 한다)이나등등을 사용할 특별한 이유가 없습니다.maplapply또는 더 엄격한 것과 같은 적용 제품군의 다른 변형vapply.

PS: 무료로 투표를 거부하는 사람들에게 OP가 다음과 같이 쓴 것을 기억하세요.

나는 여기서 구문, purrr이 제공하는 다른 기능 등에 대한 호불호를 묻는 것이 아니라 purr::map과 표준 평가를 사용한다고 가정하여 purr::map을 비교하는 것에 대해 엄격하게 묻고 있습니다.

구문 또는 기타 기능을 고려하지 않은 경우purrr사용할 특별한 이유는 없습니다.map사용합니다purrr나와 나는 해들리의 대답에 만족하지만, 아이러니하게도 OP가 그가 묻지 않은 것들을 앞에서 언급한 것들을 넘어갑니다.

tl;dr

구문이나 purr에 의해 제공되는 다른 기능에 대해 좋아하는 것과 싫어하는 것을 묻는 것이 아닙니다.

사용 사례와 일치하고 생산성을 극대화하는 도구를 선택합니다.속도 사용을 우선시하는 생산 코드의 경우*apply메모리 설치 공간을 적게 사용해야 하는 코드의 경우map으로 한, 인공학적때봤을로으간때▁basedics,,▁ergonom,map대부분의 사용자와 대부분의 일회성 작업에 적합합니다.

편리

2021년 10월 업데이트 수락된 답변과 두 번째로 가장 많이 투표된 게시물 언급 구문 편의성:

R 버전 4.1.1 이상은 이제 간단한 익명 기능을 지원합니다.\(x) 파이프 이프파|>통사론R 버전을 확인하려면 다음을 사용합니다.version[['version.string']].

library(purrr)
library(repurrrsive)
lapply(got_chars[1:2], `[[`, 2) |>
  lapply(\(.) . + 1)
#> [[1]]
#> [1] 1023
#> 
#> [[2]]
#> [1] 1053
map(got_chars[1:2], 2) %>%
  map(~ . + 1)
#> [[1]]
#> [1] 1023
#> 
#> [[2]]
#> [1] 1053

purrr작업에 목록과 같은 개체를 두 번 이상 조작하는 경우 접근 방식은 일반적으로 입력하는 데 더 짧습니다.

nchar(
"lapply(x, fun, y) |>
      lapply(\\(.) . + 1)")
#> [1] 45
nchar(
"library(purrr)
map(x, fun) %>%
  map(~ . + 1)")
#> [1] 45

사람이 경력 중에 이러한 전화를 수만 또는 수십만 통 작성할 수 있다는 점을 고려할 때, 이 구문 길이 차이는 코드가 입력된 경우 소설 1~2권(예: 소설 80,000자)을 작성하는 것과 같습니다.코드 입력 속도(분당 최대 65단어?), 입력 정확도(특정 구문을 자주 잘못 입력하는 경우가 있음)를 추가로 고려합니다.(\"<함수 인수를 호출하면 한 가지 스타일 또는 두 가지 스타일의 조합을 사용하여 생산성을 공정하게 비교할 수 있습니다.

또 다른 고려 사항은 대상 고객일 수 있습니다.개인적으로 나는 그 방법을 설명하는 것을 발견했습니다.purrr::map보다더일는하히보다 더 .lapply정확히 그것의 간결한 구문 때문에.

1 |>
  lapply(\(.z) .z + 1)
#> [[1]]
#> [1] 2

1 %>%
  map(~ .z+ 1)
#> Error in .f(.x[[i]], ...) : object '.z' not found

but,
1 %>%
  map(~ .+ 1)
#> [[1]]
#> [1] 2

속도

종종 목록과 같은 개체를 처리할 때 여러 작업이 수행됩니다.논의의 간접비에 대한 뉘앙스.purrr대부분의 코드에서 중요하지 않습니다. 큰 목록과 사용 사례를 처리합니다.

got_large <- rep(got_chars, 1e4) # 300 000 elements, 1.3 GB in memory
bench::mark(
  base = {
    lapply(got_large, `[[`, 2) |>
      lapply(\(.) . * 1e5) |>
      lapply(\(.) . / 1e5) |>
      lapply(\(.) as.character(.))
  },
  purrr = {
    map(got_large, 2) %>%
      map(~ . * 1e5) %>%
      map(~ . / 1e5) %>%
      map(~ as.character(.))
  }, iterations = 100,
)[c(1, 3, 4, 5, 7, 8, 9)]

# A tibble: 2 x 7
  expression   median `itr/sec` mem_alloc n_itr  n_gc total_time
  <bch:expr> <bch:tm>     <dbl> <bch:byt> <int> <dbl>   <bch:tm>
1 base          1.19s     0.807    9.17MB   100   301      2.06m
2 purrr         2.67s     0.363    9.15MB   100   919      4.59m

이것은 더 많은 작업을 수행할수록 분산됩니다.일부 사용자가 일상적으로 사용하는 코드를 작성하거나 패키지에 의존하는 경우 속도가 기본 R과 purr 중 하나를 선택할 때 고려해야 할 중요한 요소가 될 수 있습니다.purrr메모리 설치 공간이 약간 낮습니다.

그러나 다음과 같은 반론이 있습니다.속도를 원하는 경우 하위 수준의 언어로 이동합니다.

여기서 의 요점을 가 빨라진다는 .lapply()에는 히특 윈도을즈 R않는지경우로업때할로 하면 더해집니다.mclapply()((으)로부터parallel제가 알기로는 Windows에서 작동하지 않으며 문자 그대로 절대 작동하지 않을 것입니다.mclapply()은 구이와동다니와 .lapply()그래서 만약 당신이 코드를 쓴다면.lapply()처음부터 함수 호출의 시작 부분에 "mc"를 입력하고 사용할 여러 개의 코어를 제공하는 것 외에 코드에 대한 어떠한 변경도 필요하지 않습니다.이것은 당신이 사용하는 경우에 중요할 수 있습니다.lapply()청크로 ; 속도 ; 속도 향상 ; 속도 향상 계수lapply()사용 중인 프로세서 코어의 개수입니다.올바른 서버나 클러스터에서 코드를 사용하는 경우 몇 시간이 몇 초로 쉽게 바뀔 수 있습니다.

언급URL : https://stackoverflow.com/questions/45101045/why-use-purrrmap-instead-of-lapply

반응형