Use a function name that's a string in map loop?

Viewed 97

Some code:

mymtcars <- mtcars %>% head %>% rownames_to_column('model') %>% group_by(vs) %>% nest
mymtcars
     vs data             
  <dbl> <list>           
1     0 <tibble [3 × 11]>
2     1 <tibble [3 × 11]>

I can fit a linear model on this list column df like so:

mymtcars %>% 
+   mutate(mod = map(.x = data, ~ lm(.x$mpg ~ .x$cyl)))
# A tibble: 2 x 3
# Groups:   vs [2]
     vs data              mod   
  <dbl> <list>            <list>
1     0 <tibble [3 × 11]> <lm>  
2     1 <tibble [3 × 11]> <lm>  

What if my function name is a field?

mymtcars2 <- mtcars %>% head %>% rownames_to_column('model') %>% group_by(vs) %>% nest %>% crossing(func = c('lm'))
> mymtcars2
# A tibble: 2 x 3
     vs data              func 
  <dbl> <list>            <chr>
1     0 <tibble [3 × 11]> lm   
2     1 <tibble [3 × 11]> lm

I gave it a try with:

mymtcars2 %>% 
+   mutate(mod = map2(.x = data, .y = func, ~ .y(.x$mpg ~ .x$cyl)))
Error: Problem with `mutate()` input `mod`.
x could not find function ".y"
ℹ Input `mod` is `map2(.x = data, .y = func, ~.y(.x$mpg ~ .x$cyl))`.

How can I pass the function to call in map and then call it in the above block?

4 Answers

May be using match.fun inside map2 like below:

   models <-  mymtcars2 %>% 
       mutate(mod = map2(.x = data, .y = func, ~ match.fun(.y)(.x$mpg ~ .x$cyl)))

Output:

[[1]]

Call:
match.fun(.y)(formula = .x$mpg ~ .x$cyl)

Coefficients:
(Intercept)       .x$cyl  
  36.926733    -2.728218  


[[2]]

Call:
match.fun(.y)(formula = .x$mpg ~ .x$cyl)

Coefficients:
(Intercept)       .x$cyl  
    41.9400      -3.8025  

I also found that I can use get:

mymtcars2 %>% 
  mutate(mod = map2(.x = data, .y = func, ~ get(.y)(.x$mpg ~ .x$cyl)))

Am unsure of when to use one over the other.

A different option could be:

mymtcars2 %>%
    mutate(mod = map2(.x = data,
                      .y = func,
                      ~ exec(.y, mpg ~ cyl, data = .x)))

     vs data              func  mod   
  <dbl> <list>            <chr> <list>
1     0 <tibble [3 × 11]> lm    <lm>  
2     1 <tibble [3 × 11]> lm    <lm>  

Since {dplyr} >= 1.0 this kind of problems can be solved with dplyr::rowwise. We can use it either with a classic do.call, in which case we have to wrap the arguments in list(), or with rlang::exec. With dlpyr::rowwise we don't need map2 which makes things more readable since there is no lambda function with .x .y. However, since the output column stores lm objects (and not an atomic vector), the result has to be wrapped in mod = list(...).

library(tidyverse)

mymtcars2 %>% 
  rowwise %>% 
  mutate(mod = list(do.call(func, list(mpg ~ cyl, data = data))))
#> # A tibble: 2 x 4
#> # Rowwise: 
#>      vs data              func  mod   
#>   <dbl> <list>            <chr> <list>
#> 1     0 <tibble [3 × 11]> lm    <lm>  
#> 2     1 <tibble [3 × 11]> lm    <lm>

mymtcars2 %>% 
  rowwise %>% 
  mutate(mod = list(exec(func, mpg ~ cyl, data = data)))
#> # A tibble: 2 x 4
#> # Rowwise: 
#>      vs data              func  mod   
#>   <dbl> <list>            <chr> <list>
#> 1     0 <tibble [3 × 11]> lm    <lm>  
#> 2     1 <tibble [3 × 11]> lm    <lm>

Created on 2021-08-28 by the reprex package (v0.3.0)

Related