Passing Ellipsis Arguments to Map Function Purrr Package, R

passing ellipsis arguments to map function purrr package, R

You just need to pass the ellipses through the map_df() call as well. Otherwise they can't get into the inner f1() call.

f2 <- function(df, ...){
res <- map_df(colnames(df), ~f1(df[,.], a=., ...), ...)
return(res)
}

Passing a function and arguments to a function and purrr

We may need to take care of the cases where args returns length 0

new_func <- function(.data, .x, .fns, ...){

# Arguments
value_var_expr <- rlang::enquo(.x)
func <- .fns
func_chr <- deparse(substitute(.fns))
passed_args <- list(...)

if(length(passed_args) > 0) {

# New Param Args ----
# I do this because na.rm = TRUE when passed to say quantile gets
# converted to 1 or 100%
if ("na.rm" %in% names(passed_args)) {
tmp_args <- passed_args[!names(passed_args) == "na.rm"]
}


if (!exists("tmp_args")) {
args <- passed_args
} else {
args <- tmp_args
}
} else {
args <- NULL
}
if(length(args) == 0) args <- NULL
ret <- purrr::map(
.x = dplyr::as_tibble(.data),
.f = ~ if(is.null(args)) func(.x) else func(.x, unlist(args)) %>%
purrr::imap(.f = ~ cbind(.x, name = .y)) %>%
purrr::map_df(dplyr::as_tibble)
) %>%
purrr::imap(.f = ~ cbind(.x, sim_number = .y)) %>%
purrr::map_dfr(dplyr::as_tibble, .id = 'name') %>%
dplyr::select(sim_number, name, `.x`) %>%
dplyr::mutate(.x = as.numeric(.x)) %>%
dplyr::mutate(sim_number = factor(sim_number)) %>%
dplyr::rename(value = .x)

cn <- c("sim_number", "name", func_chr)
names(ret) <- cn
return(ret)
}

-testing

> new_func(mtcars, mpg, IQR, na.rm = TRUE)
# A tibble: 11 × 3
sim_number name IQR
<fct> <chr> <dbl>
1 mpg mpg 7.38
2 cyl cyl 4
3 disp disp 205.
4 hp hp 83.5
5 drat drat 0.84
6 wt wt 1.03
7 qsec qsec 2.01
8 vs vs 1
9 am am 1
10 gear gear 1
11 carb carb 2
> new_func(mtcars, mpg, IQR)
# A tibble: 11 × 3
sim_number name IQR
<fct> <chr> <dbl>
1 mpg mpg 7.38
2 cyl cyl 4
3 disp disp 205.
4 hp hp 83.5
5 drat drat 0.84
6 wt wt 1.03
7 qsec qsec 2.01
8 vs vs 1
9 am am 1
10 gear gear 1
11 carb carb 2
>
> new_func(mtcars, mpg, IQR, type = 7)
# A tibble: 11 × 3
sim_number name IQR
<fct> <chr> <dbl>
1 mpg mpg 7.38
2 cyl cyl 4
3 disp disp 205.
4 hp hp 83.5
5 drat drat 0.84
6 wt wt 1.03
7 qsec qsec 2.01
8 vs vs 1
9 am am 1
10 gear gear 1
11 carb carb 2

Forwarding arguments in a function with purrr::map_df

A possible solution is to create a named function which only takes one argument and pass it to map so that the only argument is the vector/list you are looping over.

Applied to your problem a solution would look like this:

# function with forwarding
read_all <- function(path, ...) {

# function within function that sets the arguments path and ellipsis as given and only leaves sheet to be determined
read_xl <- function(sheet) {
readxl::read_excel(path = path, sheet, ...)
}

path %>%
readxl::excel_sheets() %>%
rlang::set_names() %>%
purrr::map_df(read_xl)

}

# this allows you to pass along arguments in the ellipsis correctly
read_all(path)
read_all(path, col_names = FALSE)

It seems this problem is stemming from an improper environment handling of the purrr::as_mapper function. To circumvent this, I suggested using an anonymous function in the comments. Apparently, the approach below works as well.

read_all <- function(path, ...) {

path %>%
readxl::excel_sheets() %>%
rlang::set_names() %>%
purrr::map_df(function(x) {
readxl::read_excel(path = path, sheet = x, ...)
})

}

To verify that it is really the as_mapper function that is causing the problem, we can rewrite the named function-in-function from above using as_mapper. This again yields errors with and without additional arguments in the ellipsis.

# function with forwarding
read_all <- function(path, ...) {

# named mapper function
read_xl <- purrr::as_mapper(~ readxl::read_excel(path = path, sheet = .x, ...))

path %>%
readxl::excel_sheets() %>%
rlang::set_names() %>%
purrr::map_df(read_xl)

}

Update
Knowing that as_mapper is causing the issue allows us to dig deeper into the problem. Now we can inspect in the RStudio debugger what is happening under the hood when running a simple mapper version of read_excel:

read_xl <- purrr::as_mapper(~ readxl::read_excel(path = .x, sheet = .y, ...))
debugonce(read_xl)
read_xl(path, 1)

It seems that when the ellipsis is included in the mapper function, as_mapper maps the first argument not only to .x but also automatically to the ellipsis .... We can verify this by creating a simple mapper function paster taking two arguments .x and ....

paster <- purrr::as_mapper(~ paste0(.x, ...))
paster(1)
> [1] "11"
paster(2)
> [1] "22"

The question now is: is there another way we are supposed to use ellipsis in mapper functions or is this a bug.

Why do I need to quote-unquote dots when passing them to a mapped function in purrr?

I think your problem is that you need to pass the ... though each level of function call. So the ... both have to pass through map() as well as your inner function.

I could not get your example to work with nest(), so I made a version that uses select() instead

dots_fun <- function(df, foo, ...) {
df %>%
mutate(foo = foo) %>%
select(...)
}

And then, it doesn't seem you can actually use the as_mapper syntax with ... and non-standard evaluation via this github issue so you need to explicitly create an anonymous function so the iterated value isn't passed a second time in the ... values as well. Hadley said the ~ syntax is only for "simple" functions and not those with .... So a working mapping function might look like this

mapping_fun1 <- function(df, foos, ...) {
map(
.x = foos,
.f = function(x, ...) dots_fun(df = df, foo = x, ...),
...
)
}
mapping_fun1(mtcars, foos = list_of_foos, cyl, gear)

We pass the ... though map(), through our anonymous function, and finally into dots_fun. If you break that chain at any point, it falls apart.

Mapping a function in R with multiple arguments

Does this help?

If you only want to change c

map(1:4, product, a = 1, b = 2, d = 4)

If you only want to change c and d

pmap(list(1:4, 11:14), product, a = 1, b = 2)

And a suggestion: don't use c as an object name. c is the function that creates a vector. Something else is better coding style.

Passing names of objects from ellipsis as strings to left_join

Maybe rename column_b so that you don't have to worry about suffix

left_join_on_column_a <- function(keep_var, common_var, ...) {
nm = unname(sapply(rlang::enexprs(...), as.character))
keep_var <- as.character(substitute(keep_var))
common_var = as.character(substitute(common_var))

foo = function(x, y) {
x %>% select(!!common_var, !!y := !!keep_var)
}

reduce(.x = Map(foo, list(...), nm),
.f = left_join,
common_var) %>%
gather("model_type", !!keep_var, -!!common_var)
}

left_join_on_column_a(column_b, column_a, sample_one, sample_two, sample_three)

How can I pass multiple functions to purrr::map (exec or invoke_map) when function uses enquo columns?

Use rlang::call2 to compose the desired function calls, then evaluate them.

Also, because you are working with column names, the proper NSE verb is ensym(), not enquo(). The former captures symbolic names and works with strings. The latter captures an expression and its context. In this case, the context is the data frame, which is already being passed directly to the function, whereas enquo() is capturing the global environment (which is not correct).

wrap2 <- function(df, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE,
ttype=c("MK", "SK", "MKSeas")){

sim <- tribble(
~f, ~params,
"run1_fun", list(seasCOL = ensym(seasCOL), ind.obs=ind.obs), #SK
"run1_fun", list(seasCOL = NULL), #MK
"run1_fun_by", list(by="season")) #MKSeas

# Concatenate common arguments to each list of parameters
myArgs <- map( sim$params, c, yCOL=ensym(yCOL), datesCOL=ensym(datesCOL) )

# Compose the function calls
calls <- map2( sim$f, myArgs, ~rlang::call2(.x, !!!.y, df=df) )

# Evaluate the function calls and aggregate the results
map_dfr( calls, eval )
}

wrap2(dataset, Value, Date, season, ind.obs=TRUE, ttype=c("MK", "SK", "MKSeas"))
# # A tibble: 14 x 4
# tau slope type season
# <dbl> <dbl> <chr> <dbl>
# 1 -0.0259 -0.0180 SK NA
# 2 -0.0255 -0.0151 MK NA
# 3 0.2 0.0743 MK 1
# 4 -0.378 -0.162 MK 2
# 5 -0.0222 -0.0505 MK 3
# ...


Related Topics



Leave a reply



Submit