Iteration with map

Published onesixx on

https://onesixx.com/13-iterating-with-purrr/
http://r4ds.had.co.nz/iteration.html#the-map-functions
https://www.rstudio.com/resources/videos/happy-r-users-purrr-tutorial/
https://github.com/rstudio/rstudio-conf/tree/master/2017/Happy_R_Users_Purrr-Charlotte_Wickham
http://purrr.tidyverse.org/reference/map.html
http://statkclee.github.io/parallel-r/ds-fp-purrr.html
http://r4ds.had.co.nz/iteration.html#the-map-functions

https://stackoverflow.com/questions/38403111/ways-to-add-multiple-columns-to-data-frame-using-plyr-dplyr-purrr
https://www.r-bloggers.com/rebuilding-map-example-with-apply-functions/

purrr에서 map()만 사용한다면 큰 의미가 없다.
https://jtr13.github.io/spring19/ss5593&fq2150.html

pacman::p_load("repurrrsive")
# sw_films 각 영화 7개의 list, 각 영화는 14개의 list로 이루어짐. 
sw_films %>% lapply(function(x){x[["director"]]})
sw_films %>% map(~ .x[["director"]])
sw_films %>% map(~ .[["director"]])

purrr의 장점

exlist <- list(1,2,3)
map(exlist, ~(.x+1))
# [[1]]
# [1] 2
# [[2]]
# [1] 3
# [[3]]
# [1] 4
  • 일관된 문법 , 일관된 return type
    (sapply() & lapply()는 return이 상황에 따라 다르다.)
  • Pipe사용가능
  • 함수의 인수(mean()함수의  trim이나 na.rm )을 바로 붙여 사용할수 있다. 
dt <- data.table(a=rnorm(10), b=sample(100,10), c=rchisq(10,2), d=rbinom(10,100,0.5))
map_dbl(dt, mean, trim=0.5)
#          a           b           c           d 
# 0.06995195 84.00000000  2.15738130 51.00000000 
  • map functions 은  name을 유지한다.
z <- list(x=1:3, y=4:5)
map_int(z, length) 
# x y 
# 3 2 
lapply(z, length) %>% as.integer()
# [1] 3 2
  • 간결한 shortcuts .
     . 은 대명사  (for루프에서 i가 현재 index를 대신하듯이,  . 은 현재 list요소를 대신한다)
    function(x){ x+1 } 은 ~ .+1
    즉 X %>% lapply(function(x) x+1) 은 X %>% map(~{.+1})
lapply(list, function(x) x[[2]])
map(list, function(x) x[[2]])
map(list, 2) 
mtcars %>% split(.$cyl) %>%    
#map(function(df) lm(mpg ~ wt, data = df)) 
map(~ lm(mpg ~ wt, data=.)) %>% 
map(summary) %>% 
#extracting named components is a common operation models 
#map_dbl(~.$r.squared )      
map_dbl(  "r.squared") 
  • 가져올 요소의 위치(Position)을 Integer를 사용하여 대신할수 있다.
x <- list(list(1, 2, 3),
          list(4, 5, 6),
          list(7, 8, 9)) 
x %>% map_dbl(2) 
#> [1] 2 5 8 

apply와 비교

sapply(iris[ ,-5], mean, na.rm=T)
apply(iris[ ,-5], 2, mean, na.rm=T)
iris[ ,-5] %>% apply(2,mean,na.rm=T)

iris[ ,-5] %>% map_dbl(mean,na.rm=T)

반복문

1:nrow(iris) %>% 
  map(function(rowNum){ 
        dataRow <- iris[rowNum,]
      })\t

Map() 의 종류

map_returnType

map functionmakes a 
map()list lapply(), sapply()
map_lgl()     logical vector 
map_int()     integer vector 
map_dbl()     double vector 
map_chr()character vector 

각 map 함수는 vector를 input으로 받아서, 각 vector에 함수를 적용하고,
같은이름과 length의 새로운 vector를 output으로 return한다.
(map 함수의 접미사는 vector 유형을 결정한다.) 

for루프의 속도는 많이 향상되었기 때문에, Map 함수는 속도보다는 코드의 간결/정확함을 위해 사용한다고 생각하면 된다. 

map_if(), map_at()

numbers  <-  list(11, 12, 13, 14)
is_even  <-  function(x){ !as.logical(x %% 2) } # number가 짝수이면, TRUE

map_if(numbers, is_even, sqrt)
# [[1]]
# [1] 11
# [[2]]
# [1] 3.464102
# [[3]]
# [1] 13
# [[4]]
# [1] 3.741657

map_at(numbers, c(1,3), sqrt)
# [[1]]
# [1] 3.316625
# [[2]]
# [1] 12
# [[3]]
# [1] 3.605551
# [[4]]
# [1] 14

argument가 여러개일때 Mapping

map2(.x, .y, .f, ...)

set.seed(666)
options(digits=2)

mu    <- list(5,10,-3)
sigma <- list(1, 5,10)
n     <- list(1, 3, 5)

# map(mu, rnorm, n=5)
# map(seq_along(mu), ~rnorm(n=5, mean=mu[[.x]], sd=sigma[[.x]]))
map2(mu, sigma, rnorm, n=5)

pmap(.l, .f, …)

arg1 <- list(n, mu, sigma)
args2 <- list(mean = mu, sd = sigma, n = n)

pmap(arg1, rnorm)
pmap(arg2, rnorm)

example.

x <- list(1, 10, 100)
y <- list(1,  2,   3)
z <- list(5, 50, 500)
l <- list(a=x, b=y, c=z)

map2(x, y, `+`)
map2(x, y, ~{.x +.y})

pmap(list(x, y, z), sum)
# Matching arguments by position
pmap(list(x, y, z), function(a, b, c){ a/(b+c)})
# Matching arguments by name
pmap(l, function(a, b, c){ a/(b+c) })
# Split into pieces, fit model to each piece, then predict
by_cyl <- mtcars %>% split(.$cyl)
mods <- by_cyl %>% map(~ lm(mpg ~ wt, data = .))
map2(mods, by_cyl, predict)

# Vectorizing a function over multiple arguments
df <- data.frame(
  x = c("apple", "banana", "cherry"),
  pattern = c("p", "n", "h"),
  replacement = c("x", "f", "q"),
  stringsAsFactors = FALSE
)
pmap(df, gsub)
pmap_chr(df, gsub)

function이 여러개일때 Mapping

invoke_map() 함수 

f <- c("runif", "rnorm", "rpois") 
param <- list(
 list(min = -1, max = 1), list(sd = 5), list(lambda = 10)
)
invoke_map(f, param, n = 5) %>% str()
sim <- tribble(
         ~f, ~params,
    "runif", list(min = -1, max = 1), 
    "rnorm", list(sd = 5),
    "rpois", list(lambda = 10)
)
sim %>% mutate(sim = invoke_map(f, params, n = 10))

SOLVE ITERATION PROBLEMS 
FOR EACH __DO __
You are already solving them:
copy & paste, for loops, (l/s)apply()
I'll show you an alternative purrr::map() & friends

library(purrr)
library(tidyverse)
load("~/Dropbox/Rhome/purr/data/swapi.rda")

map( .x, .f, ...)
for each element of .x do .f

Other types of output            : map_lgl( .x, .f, ...)

  • map_lgl() logical vector
  • map_int() integer vector
  • map_dbl() double vector
  • map_chr() character vecto

Other ways of specifying .f  : map( .x, length, ...)
Other iteration functions     : map2( .x, .y, .f, ...)

map() 항상 list를 return한다.

identical 

map(.x, .f = some_function, ...)
map(.x, ~ some_function(.x, ...))

map(people, ~ .x[["hair_color"]])

map(people, .f = "hair_color")

map(people, "hair_color")

Dealing with failure

http://r4ds.had.co.nz/iteration.html#dealing-with-failure

여러 operation을 반복하기 위해 map 함수를 사용하다보면 그만큼 operation이 오류를 일으킬 확률이 높아진다.
하나때문에 모든 operation이 안돌아가고, 그게 어떤건지 찾는건 좀 짜증나는 일이다. 

safely(.f, otherwise = NULL, quiet = TRUE)

quietly(.f)

possibly(.f, otherwise, quiet = TRUE)

safely()

뭔가 잘못되더라도, 함수가 실행되는 것을 멈추지 한는다.
Base R의 try와 비슷한 safely 는 함수를 감싸서 결과를 두가지로 알려준다. 

  • result is the original result.   If there was an error,                       this will be NULL.
  • error is an error object.          If the operation was successful, this will be NULL.
log(10)   # 2.302585
safe_log <- safely(log)
  • 정상적인 경우
str(safe_log(10))

c(10) %>% map(safely(log)) %>% str()
List of 2
 $ result: num 2.3
 $ error : NULL
  • 에러상황 : 문자에 log를 취하려고 함
str(safe_log("a"))

c("a") %>% map(safely(log)) %>% str()
List of 2
 $ result: NULL
 $ error :List of 2
  ..$ message: chr "non-numeric argument to mathematical function"
  ..$ call   : language .f(...)
  ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

ex2>   https://www.r-bloggers.com/lesser-known-purrr-tricks/

safely()   는 아래  possibly() 와 비슷하지만  list의 list를 return한다. (result리스트와 error 메세지)

numbers_with_error <- list(1, 2, 3, "spam", 4)
safe_sqrt <- safely(sqrt, otherwise = NA_real_)
map(numbers_with_error, safe_sqrt)
possible_sqrt <- possibly(sqrt, otherwise = NA_real_)
map(numbers_with_error, possible_sqrt)

possibly()

에러상황: 정의된 값으로 대체한다. 

x <- list(1, 10, "a")
x %>% map_dbl(possibly(log, NA_real_))
[1] 0.00 2.30       NA

quietly()

safely()와 비슷하지만, 에러를 잡는대신 printed output, messages, warnings을 잡는다.

x <- list(1, -1)
x %>% map(quietly(log)) %>% str()
Categories: R Tidyverse

onesixx

Blog Owner

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x