Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

mutating variables (symbol or string) with tidy evaluation

Tags:

r

dplyr

tidyeval

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?

function

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 }})))
}

works as expected

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

doesn't work as expected

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
like image 364
Indrajeet Patil Avatar asked Dec 19 '25 12:12

Indrajeet Patil


1 Answers

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]))
}
like image 187
Lionel Henry Avatar answered Dec 22 '25 03:12

Lionel Henry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!