I want to use tidy evaluation in my function while mutating the entered variable, but can't seem to get the function to work properly.
I have read both tidy eval with e.g. mtcars %>% mutate(target := log(target)) and Tidy Evaluation not working with mutate and stringr, and they seem to suggest that there needs to be a different solution based on whether a symbol or a string is passed as an argument. But I'd prefer a general solution to this problem that works with both of these inputs.
Is there a way to do this?
library(magrittr)
foo <- function(data, x) {
# I know this is one solution to solve my problem
# But I want to avoid it because it creates problem if the function is to be used within a loop
# x <- rlang::ensym(x)
df <- tibble::as_tibble(dplyr::select(data, {{ x }}))
df %>% dplyr::mutate(.data = ., {{ x }} := droplevels(as.factor({{ x }})))
}
foo(mtcars, am)
#> # A tibble: 32 x 1
#> am
#> <fct>
#> 1 1
#> 2 1
#> 3 1
#> 4 0
#> 5 0
#> 6 0
#> 7 0
#> 8 0
#> 9 0
#> 10 0
#> # ... with 22 more rows
foo(mtcars, "am")
#> # A tibble: 32 x 1
#> am
#> <fct>
#> 1 am
#> 2 am
#> 3 am
#> 4 am
#> 5 am
#> 6 am
#> 7 am
#> 8 am
#> 9 am
#> 10 am
#> # ... with 22 more rows
P.S. In case you are curious about the issue caused in loops by the possible solution, here is a reprex:
library(magrittr)
col.name <- colnames(mtcars)
foo <- function(data, x) {
x <- rlang::ensym(x)
df <- tibble::as_tibble(dplyr::select(data, {{ x }}))
df %>% dplyr::mutate(.data = ., {{ x }} := droplevels(as.factor({{ x }})))
}
for (i in 3:length(mtcars)) {
foo(mtcars, col.name[i])
}
#> Error: Only strings can be converted to symbols
It is generally not recommended to try and make your function compatible with both masked columns and column names, because this is not how tidyverse functions are designed to work.
First choose whether you want to take actions or selections. If you take the latter, then you can use tidyselect features like all_of() for compatibility with character vectors. An easy way to take selections in all dplyr verbs is to use across().
In this case you also use select() to begin with, so we can just use across(everything()). In general, you could do across({{ x }}).
foo <- function(data, x) {
data %>%
as_tibble() %>%
select({{ x }}) %>%
# Could also remove the `select()` step and pass `{{ x }}` to across()
mutate(across(everything(), ~ droplevels(as.factor(.))))
}
foo(mtcars, am)
Since you've forwarded x to a selection context, your function now supports all tidyselect features. If you need to pass a character vector or a string, just use all_of(). Adapting your example:
nms <- names(mtcars)
for (i in 3:length(mtcars)) {
foo(mtcars, all_of(nms[i]))
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With