Reputation: 15072
As I understand, normally you don't need to quote or unquote dots when they are not being modified (e.g. by changing their names). This example makes it seem like I don't really understand how that works, however.
Here we have a function that relies on dots to select columns for nesting. All it does is add a column from the foo
argument, and then nest all columns not mentioned in the dots.
library(tidyverse)
dots_fun <- function(df, foo, ...) {
df %>%
mutate(foo = foo) %>%
nest(data = -c(...))
}
dots_fun(mtcars, "a", cyl)
#> # A tibble: 3 x 2
#> cyl data
#> <dbl> <list>
#> 1 6 <tibble [7 × 11]>
#> 2 4 <tibble [11 × 11]>
#> 3 8 <tibble [14 × 11]>
I want to be able to map this function, by calling it with different arguments. A naive approach to doing this by using a normal anonymous function syntax fails with a confusing error:
list_of_foos <- c("a", "b")
mapping_fun1 <- function(df, foos, ...) {
map(
.x = foos,
.f = ~ dots_fun(df = df, foo = .x, ...)
)
}
mapping_fun1(mtcars, foos = list_of_foos, cyl)
#> Error: Can't subset columns that don't exist.
#> x The column `a` doesn't exist.
Nor does it help if I just move the dots outside of the anonymous function. It no longer errors, but fails to nest on cyl
as desired.
mapping_fun2 <- function(df, foos, ...) {
map(
.x = foos,
.f = ~ dots_fun(df = df, foo = .x),
...
)
}
mapping_fun2(mtcars, foos = list_of_foos, cyl)
#> [[1]]
#> # A tibble: 1 x 1
#> data
#> <list>
#> 1 <tibble [32 × 12]>
#>
#> [[2]]
#> # A tibble: 1 x 1
#> data
#> <list>
#> 1 <tibble [32 × 12]>
I managed to get it to work by splicing the dots into the anonymous function, but I don't really understand why this was necessary. (You can also get it to work by reversing the order of the arguments of the mapped function and supplying all arguments through the ...
of map
, but then dots_fun
has the "wrong" argument order. It doesn't work if you use a function()
style anonymous function to reverse the argument order)
mapping_fun3 <- function(df, foos, ...) {
dots <- enquos(...)
map(
.x = foos,
.f = ~ dots_fun(df = df, foo = .x, !!!dots)
)
}
mapping_fun3(mtcars, foos = list_of_foos, cyl)
#> [[1]]
#> # A tibble: 3 x 2
#> cyl data
#> <dbl> <list>
#> 1 6 <tibble [7 × 11]>
#> 2 4 <tibble [11 × 11]>
#> 3 8 <tibble [14 × 11]>
#>
#> [[2]]
#> # A tibble: 3 x 2
#> cyl data
#> <dbl> <list>
#> 1 6 <tibble [7 × 11]>
#> 2 4 <tibble [11 × 11]>
#> 3 8 <tibble [14 × 11]>
My question is: in what condition/situation do you need to quote and unquote ...
to pass them safely through functions? and how does that condition apply here?
Upvotes: 2
Views: 214
Reputation: 206253
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.
Upvotes: 3