4

I would like to change the values in a specific column to include information from another column using the glue function.

I do it normally like this:

library(glue)
library(dplyr)
df = data.frame(x = c("Banana","Apple","Melon"),
                y = c(10,15,27),
                z = rep(c("something_else"),3))
df %>%
  mutate(x = glue("{x} ({y})"))

The output:

#>             x  y              z
#> 1 Banana (10) 10 something_else
#> 2  Apple (15) 15 something_else
#> 3  Melon (27) 27 something_else

My proplem arises when I try to do the same thing using the dataframe and the column names as inputs in a user defined function.

My intitial instict is to pass the inputs using double curly braces in conjunction with the glue function, but this results in an error.

concatenate_value_to_string <- function(tbl,var1,var2) {
  tbl %>%
    mutate({{var1}} := glue("{{{var1}}} ({{{var2}}})"))
}

concatenate_value_to_string(df,x,y)
#> Error in UseMethod("mutate"): no applicable method for 'mutate' applied to an object of class "function"

Created on 2021-08-02 by the reprex package (v2.0.0)

Clearly triple curly braces are not the solution here, could anyone help me out?

Thank you.

Ran K
  • 162
  • 1
  • 5

3 Answers3

4

You could use dplyr::pull():

concatenate_value_to_string <- function(tbl,var1,var2) {
  tbl %>%
    mutate({{var1}} :=  glue("{pull(., {{var1}})} ({pull(., {{var2}})})"))
}

concatenate_value_to_string(df,x,y)

concatenate_value_to_string(df,x,y)
#>             x  y              z
#> 1 Banana (10) 10 something_else
#> 2  Apple (15) 15 something_else
#> 3  Melon (27) 27 something_else

Or eval(rlang::expr()), where we first build the symbol from the input then evaluate it in the context of the data frame.

concatenate_value_to_string <- function(tbl,var1,var2) {
  tbl %>%
    mutate({{var1}} :=  glue("{eval(expr({{var1}}))} ({eval(expr({{var2}}))})"))
}

concatenate_value_to_string(df,x,y)
#>             x  y              z
#> 1 Banana (10) 10 something_else
#> 2  Apple (15) 15 something_else
#> 3  Melon (27) 27 something_else

What you tried doesn't work because mutate() doesn't substitute {{foo}} if it's part of a string, in the solutions above, pull() or expr() do it.

Personally I'd rather use sprintf() in this case:

concatenate_value_to_string <- function(tbl,var1,var2) {
  tbl %>%
    mutate({{var1}} :=  sprintf("%s (%s)", {{var1}}, {{var2}}))
}

concatenate_value_to_string(df,x,y)
#>             x  y              z
#> 1 Banana (10) 10 something_else
#> 2  Apple (15) 15 something_else
#> 3  Melon (27) 27 something_else
moodymudskipper
  • 46,417
  • 11
  • 121
  • 167
1

Another option could be:

concatenate_value_to_string <- function(tbl, var1, var2) {
    tbl %>%
        mutate(!!var1 := glue("{.data[[var1]]} ({.data[[var2]]})")) 
}

concatenate_value_to_string(df, "x", "y")

            x  y              z
1 Banana (10) 10 something_else
2  Apple (15) 15 something_else
3  Melon (27) 27 something_else
tmfmnk
  • 38,881
  • 4
  • 47
  • 67
1

Here is another option for when you would like to pass argument names instead of strings. Function enquo is used to diffuse user-defined variables, but instead of ensym which returns a raw expression enquo returns a quosure which is an expression bound to an environment. So we use get_expr to access its expression and wrap it with rlang::eval_tidy or base::eval to be evaluated in the context.

library(rlang)

concatenate_value_to_string <- function(tbl, var1, var2) {
  tbl %>%
    mutate(!!enquo(var1) := glue("{eval_tidy(get_expr(enquo(var1)))} ({eval_tidy(get_expr(enquo(var2)))})"))
}

concatenate_value_to_string(df, x, y)
            x  y              z
1 Banana (10) 10 something_else
2  Apple (15) 15 something_else
3  Melon (27) 27 something_else
Anoushiravan R
  • 21,622
  • 3
  • 18
  • 41