Jmac
Jmac

Reputation: 243

How can I pass multiple functions to purrr::map (exec or invoke_map) when function uses enquo columns?

I get the following error when I try to pass arguments to a function that uses enquo().

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

In the example below,

Wrap1 function uses map to run multiple argument sets for a single function. Passing the variables with !!enquo() works.

Wrap 2 tries to pass multiple functions and arguments, but I can't get the syntax correct.

  1. Is there an issue with the how I've pass (either order in exec or invoke_map functions, or how the columns are passed with !!enquo(cols)?

  2. Is there a way to pass extra variables to invoke_map or that are common to all outside the param list (similar to using partial in the wrap1 code below)?

library(dplyr)  
library(lubridate)  
library(purrr)   
library(rlang)   
library(rkt)

dataset <- tibble(Date= lubridate::decimal_date(seq(as.Date("2000/1/1"), by = "month", length.out = 120)),
                  Value = rnorm(120), 
                  season = lubridate::month(seq(as.Date("2000/1/1"), by = "month", length.out = 120)))
  
#inner function
run1_fun <- function(df, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE){
  
  y <- df %>% pull(!!enquo(yCOL))
  dates <- df %>% pull(!!enquo(datesCOL))
   
  mk <- rlang::quo_is_null(enquo(seasCOL))

  if (mk){
    seas <- rep(1, length(y))  #just so can get nseas=1 later
    smk <- rkt::rkt(date=dates, y=y)
  } else {
    seas <- df %>% pull(!!enquo(seasCOL))
    smk <- rkt::rkt(date=dates, y=y, block=seas, correct = !ind.obs)
  }

  out <- tibble(tau = smk$tau, 
                slope = smk$B, 
                type = ifelse(mk, "MK", "SK"))
  out
}

#inner function with by option
run1_fun_by <- function(df, by=NULL, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE){
  
    if(is.null(by)) {
      df <- run1_fun(df, !!enquo(yCOL), !!enquo(datesCOL), !!enquo(seasCOL),ind.obs)
  } else {
    df <- plyr::ddply(df, .variables = by, 
                     .fun = run1_fun, !!enquo(yCOL), !!enquo(datesCOL), !!enquo(seasCOL),ind.obs)
  }
  df
}

#to run:
run1_fun_by(dataset, by="season", Value, Date, ind.obs=TRUE)

#WRAP 1: WORKS
# example of wrap function that passing multiple arguments sets to the above inner function that uses enquo variables. 
wrap1 <- function(df, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE, 
                  ttype=c("MK", "SK")){

  fun_args <- partial(run1_fun, df=df, yCOL = !!enquo(yCOL), datesCOL =  !!enquo(datesCOL), ind.obs=ind.obs)
             
  #fun_args - order in "SK", "MK"  
  keep <- which(c("SK", "MK") %in% ttype)
  args <- list(seasCOL = dplyr::vars(!!enquo(seasCOL), NULL)[keep])   # didnt work with alist..
  #args <-tibble(seasCOL = dplyr::vars(!!enquo(seasCOL), NULL)[keep])  #works too
  res <- purrr::pmap_dfr(args, fun_args)
  
  res
}
#to run: 
wrap1(dataset, Value, Date, season, ind.obs=TRUE, ttype=c("MK", "SK"))

#WRAP 2: DOESN'T WORK
#attempt to iterate through multiple functions and argument sets (where functions requires enquo arguments)
using either invoke_map or exec?

wrap2 <- function(df, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE, 
                  ttype=c("MK", "SK", "MKSeas")){
  
  t <- c("SK", "MK", "MKSeas")
  
   #HOW CAN I PASS (which are enq in next function)
    sim <- tribble(
   ~f,               ~params,
  "run1_fun",        list(seasCOL = dplyr::vars(!!enquo(seasCOL)), ind.obs=ind.obs),   #SK
  "run1_fun",        list(seasCOL = dplyr::vars(NULL)),                                      #MK
  "run1_fun_by",     list(by="season"))                                                #MKSeas
  
  #with invoke_map - but looks like this is retired 
  res  <- invoke_map_dfc(sim$f, sim$params, df=df, yCOL=!!enquo(yCOL), datesCOL=!!enquo(datesCOL)) 
  
  #with exec - new
  #res <-  map2_dfc(sim$f, sim$params, function(fn, args) exec(fn, !!!args))

  res  
}

#to run
wrap2(dataset, Value, Date, season, ind.obs=TRUE, ttype=c("MK", "SK", "MKSeas"))

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

Bad:

list(!!myquosure)

Good:

dplyr::mutate(data, !!myquosure) Call rlang::last_error() to see a backtrace Called from: abort(paste_line("Quosures can only be unquoted within a quasiquotation context.", "", " # Bad:", " list(!!myquosure)", "", " # Good:", " dplyr::mutate(data, !!myquosure)")

Upvotes: 1

Views: 921

Answers (1)

Artem Sokolov
Artem Sokolov

Reputation: 13731

Use rlang::call2 to compose the desired function calls, then evaluate them.

Also, because you are working with column names, the proper NSE verb is ensym(), not enquo(). The former captures symbolic names and works with strings. The latter captures an expression and its context. In this case, the context is the data frame, which is already being passed directly to the function, whereas enquo() is capturing the global environment (which is not correct).

wrap2 <- function(df, yCOL, datesCOL, seasCOL=NULL,  ind.obs=TRUE,
                  ttype=c("MK", "SK", "MKSeas")){

  sim <- tribble(
          ~f,             ~params,
          "run1_fun",     list(seasCOL = ensym(seasCOL), ind.obs=ind.obs),  #SK
          "run1_fun",     list(seasCOL = NULL),                             #MK
          "run1_fun_by",  list(by="season"))                                #MKSeas

  # Concatenate common arguments to each list of parameters
  myArgs <- map( sim$params, c, yCOL=ensym(yCOL), datesCOL=ensym(datesCOL) )

  # Compose the function calls
  calls <- map2( sim$f, myArgs, ~rlang::call2(.x, !!!.y, df=df) )

  # Evaluate the function calls and aggregate the results
  map_dfr( calls, eval )
}

wrap2(dataset, Value, Date, season, ind.obs=TRUE, ttype=c("MK", "SK", "MKSeas"))
# # A tibble: 14 x 4
#        tau   slope type  season
#      <dbl>   <dbl> <chr>  <dbl>
#  1 -0.0259 -0.0180 SK        NA
#  2 -0.0255 -0.0151 MK        NA
#  3  0.2     0.0743 MK         1
#  4 -0.378  -0.162  MK         2
#  5 -0.0222 -0.0505 MK         3
# ...

Upvotes: 1

Related Questions