Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define key argument of gather function using string concatenation

Tags:

r

dplyr

tidyeval

I've got a tibble with interaction of few factors as a column names (see example with two factors below).

ex <- structure(list(`Monday*FALSE` = 42.74, `Monday*TRUE` = 70.68, 
`Tuesday*TRUE` = 44.05, `Tuesday*FALSE` = 51.25, `Wednesday*TRUE` = 35.57, 
`Wednesday*FALSE` = 59.24, `Thursday*TRUE` = 85.3, `Thursday*FALSE` = 59.91, 
`Friday*TRUE` = 47.27, `Friday*FALSE` = 47.44, `Saturday*TRUE` = 62.28, 
`Saturday*FALSE` = 98.8, `Sunday*TRUE` = 57.11, `Sunday*FALSE` = 65.99), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -1L))

I want to write a function that would allow to gather this tibble, but additionally create a key name based on input names of factors. However, the following doesn't work as intented because paste0 returns a string.

my_gather <- function(data, ...){
  vars <- enquos(...)
  data %>% 
    gather(key = paste0(!!!vars, sep = '*'), value = value, factor_key = TRUE)
}

my_gather(ex, day, cond) %>% head()
# A tibble: 6 x 2
  `paste0(day, cond, sep = ".")` value
  <fct>                          <dbl>
1 Monday*FALSE                    42.7
2 Monday*TRUE                     70.7
3 Tuesday*TRUE                    44.0
4 Tuesday*FALSE                   51.2
5 Wednesday*TRUE                  35.6
6 Wednesday*FALSE                 59.2

I have tried to replace * by . to make a valid synctatic name, then capture paste0 into a sym with !!:

my_gather <- function(data, ...){
   vars <- enquos(...)
   data %>% 
     gather(key = !!sym(paste0(!!!vars, sep = '.')), value = value, factor_key = TRUE)
}

but it results in an error:

Error in !vars : invalid argument type

gather seems to quotate the key and value arguments if neccessary, so is there any way to evaluate paste0(...) within key definition?

like image 425
Kuba_ Avatar asked Sep 11 '18 11:09

Kuba_


1 Answers

This doesn't work because you're double unquoting:

!!sym(paste0(!!!vars, sep = '.'))

Everything inside !! is evaluated normally, so if you use another unquote operator it needs to be handled by another quasiquoting function. paste0() does not support !!!.

As a general rule, it is better to do things in several steps with complex syntax like !!. It is more readable and there's fewer chances of making mistakes.

The second thing is that you're quoting the inputs with enquos(). This means they can be any complex expressions instead of column names. If you're expecting bare columns, it's better to use ensyms(...) instead (or just syms(c(...)) if you prefer to take strings without any quoting).

my_gather <- function(data, ...){
  # ensyms() guarantees there can't be complex expressions in `...`
  vars <- ensyms(...)

  # Let's convert all symbols to strings and return a character vector
  keys <- purrr::map_chr(vars, as.character)

  # Now we can use paste() the normal way. It doesn't support `!!!`
  # but the standard way of dealing with vector inputs is the
  # `collapse` argument:
  key <- paste0(keys, collapse = '*')

  # Equivalently, but weird:
  key <- eval(expr(paste(!!!keys, sep = "*")))

  # Now the key can be unquoted:
  data %>%
    gather(key = !!key, value = value, factor_key = TRUE)
}
like image 117
Lionel Henry Avatar answered Nov 03 '22 06:11

Lionel Henry