3

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}"))
}
Dylan Russell
  • 936
  • 1
  • 10
  • 29

2 Answers2

5

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

Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74
1

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
Paul
  • 8,734
  • 1
  • 26
  • 36
  • My thought was I would just replicate the call `glue::glue_data(df, "{lastName}, {firstName}"` but with NSE. Interesting that it wont work that way. Please see my edit for why I'm not sure I can implement your solution. – Dylan Russell Nov 18 '20 at 06:26
  • You'll see that I can't call `as_name` on it upfront because I still need to force the evaluation of `last` and `first` earlier in a pipe. – Dylan Russell Nov 18 '20 at 06:33
  • `as_name` can be called inside `glue`. See my updated answer. – Paul Nov 18 '20 at 06:42
  • Why does `glue` have to be called within `glue_data`? I can't follow what it's doing. – Dylan Russell Nov 18 '20 at 06:54
  • The inner `glue` is constructing the expression string. Consider it in two steps: `glue_string <- "{lastName}, {firstName}"` and `glue_data(data, glue_string)`. The first step can be rewritten as `glue_string <- glue("{[as_name(last)]}, {[as_name(first)]}", .open = "[", .close = "]")`. – Paul Nov 18 '20 at 07:06