R use bang-bang within a glue statement

Viewed 551

I'd like to make a simple function that takes a data frame and user supplied names for two columns in that data frame. The purpose would be to allow this to work easily with dplyr pipes. It will produce a character vector of glued strings:

func <- function(data, last, first) {
  last <- rlang::enquo(last)
  first <- rlang::enquo(first)
  
  glue::glue_data(data, "{!!last}, {!!first}")
}

I'd ideally like a user to be able to call:

df %>% func(lastName, firstName)

This would produce a character vector of multiple values formatted as Smith, John.

My function currently does not work because the bang-bang operators don't work within the context of glue_data. How can I work around this while still using NSE? The error I receive is:

Error: Quosures can only be unquoted within a quasiquotation context.

REPREX:

df <- data.frame(lastName = c("Smith", "Bond", "Trump"), firstName = c("John","James","Donald"))

> df
  lastName firstName
1    Smith      John
2     Bond     James
3    Trump    Donald

EXPECTED OUTPUT

> glue::glue_data(df, "{lastName}, {firstName}")

Smith, John
Bond, James
Trump, Donald

However, I would like to be able to achieve the expected output by using my function and calling:

df %>% func(lastName, firstName)

Above is a simplified version of my actual use case where I will actually be calling the glue statement as a parameter in a follow on function:

biggerfn <- function(data, subject, first, last) {
  subject <- rlang::enquo(subject)
  first <- rlang::enquo(first)
  last <- rlang::enquo(last)
  
  data %>%
    dplyr::distinct(!!subject, !!first, !!last) %>%
    smallerfunc(!!subject, glue::glue_data(data, "{!!last}, {!!first}"))
}
2 Answers

I don't know if you're committed to glue, but this can be easily accomplished using tidyr::unite:

func <- function(data, last, first) {
    data %>%
        tidyr::unite(result, {{last}}, {{first}}, sep=", ")
}

df %>% func(lastName, firstName)
#           result
#  1   Smith, John
#  2   Bond, James
#  3 Trump, Donald

# Optionally, follow up with: 
#   %>% dplyr::pull(result)
# to retrieve the column

Here, {{x}} is a shorthand for !!enquo(x).

I don't think you want to force the evaluation of first and last. You just want the names to construct the string "{lastName}, {firstName}".

library(dplyr)
library(rlang)
library(glue)

func <- function(data, last, first) {
  first <- as_name(enquo(first))
  last <- as_name(enquo(last))
  
  glue_data(
    data,
    glue("{[last]}, {[first]}", .open = "[", .close = "]")
  )
}

df %>% func(lastName, firstName)
#> Smith, John
#> Bond, James
#> Trump, Donald

For your new example, call as_name inside the inner glue call.

df <- data.frame(
  lastName = c("Smith", "Bond", "Trump"),
  firstName = c("John","James","Donald"),
  other = 1:3
)

biggerfn <- function(data, subject, first, last) {
  subject <- enquo(subject)
  first <- enquo(first)
  last <- enquo(last)
  
  data %>%
    distinct(!!subject, !!first, !!last) %>%
    transmute(
      !!subject := glue_data(
        data,
        glue("{[as_name(last)]}, {[as_name(first)]}", .open = "[", .close = "]")
      )
    )
}

biggerfn(df, other, firstName, lastName)
#>           other
#> 1   Smith, John
#> 2   Bond, James
#> 3 Trump, Donald
Related