【问题标题】:How can I pass multiple functions to purr::map (exec or invoke_map) when function uses enquo columns?当函数使用 enquo 列时,如何将多个函数传递给 purr::map(exec 或 invoke_map)?
【发布时间】:2020-02-24 00:50:31
【问题描述】:

当我尝试将参数传递给使用 enquo() 的函数时,出现以下错误。

“错误:只能在 quasiquotation 上下文中不引用引文。”

在下面的例子中,

Wrap1 函数使用 map 为单个函数运行多个参数集。使用 !!enquo() 传递变量有效。

Wrap 2 尝试传递多个函数和参数,但我无法获得正确的语法。

  1. 我的传递方式是否存在问题(exec 或 invoke_map 函数中的顺序,或者列如何通过 !!enquo(cols) 传递?

  2. 有没有办法将额外的变量传递给 invoke_map 或参数列表之外的所有变量(类似于在下面的 wrap1 代码中使用 partial)?

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

错误:Quosures 只能在 quasiquotation 上下文中不被引用。

不好:

列表(!!myquosure)

好:

dplyr::mutate(数据, !!myquosure) 致电rlang::last_error() 查看回溯 调用自:abort(paste_line("Quosures 只能在 quasiquotation 上下文中不被引用。", "", "# Bad:", "list(!!myquosure)", "", " # Good:", " dplyr::mutate(data, !!myquosure)")

【问题讨论】:

    标签: r purrr rlang


    【解决方案1】:

    使用rlang::call2 组合所需的函数调用,然后评估它们。

    另外,因为您使用的是列名,所以正确的 NSE 动词是 ensym(),而不是 enquo()。前者捕获符号名称并使用字符串。后者捕获一个表达式及其上下文。在这种情况下,上下文是数据帧,它已经被直接传递给函数,而enquo() 正在捕获全局环境(这是不正确的)。

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

    【讨论】:

    • 谢谢。确认 - 每当我在函数中引用 datafrme 中的列时,我应该使用 ensym 代替吗?使用 enquo 似乎在 wrap1 函数中工作 - 这纯粹是因为我没有在全局环境中定义的同名变量吗? @artem
    • 确认 - 每当我在函数中引用 datafrme 中的列时,我应该改用 ensym 吗? - 例如,在内部函数中也是如此?
    • @Jmac: 是的,一般的经验法则是:1) 在引用数据框中的列时使用 ensym(),该列可通过另一个变量立即访问,2) 对表达式使用 enexpr()有意义的是本地上下文(例如,数据框中的mutate()),3)使用enquo() 表示必须在定义它们的环境中进行评估(在嵌套函数中很有用不要传递数据框)。
    猜你喜欢
    • 1970-01-01
    • 2019-03-21
    • 2021-10-14
    • 2021-11-21
    • 2023-03-21
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多