Home > database >  How to undo ensym and specify the environment for a function within a function?
How to undo ensym and specify the environment for a function within a function?

Time:03-29

So, I'm asking this as a follow-up to another question, to the solution to which I thought would fix all of my problems. It seems like that's not the case. Take the following setup

library(tidyverse)

set.seed(1) 

mytib <- tibble(a = as.character(c(1:5, NA)), 
                b = as.character(c(6:8, NA, 9:10)), 
                c = as.character(sample(x = c(0,1), size = 6, replace = TRUE)))

vars <- c("a", "b")

Taking the function Ritchie Sacramento created in the other post

convert_tib <- function(tib, var) {
  tib %>% 
    transmute(across(c(var, c), as.integer)) %>%
    filter(!is.na(.data[[var]]))
}

I would like to add another function call within that function. The idea is that I first transform and filter my variables (code above) and then I feed it into this function experiment::ATEnocov(Y = a, Z = c, data = tib) to obtain the average treatment effect for the transformed and filtered data. I'd then like to run the whole function with purrr:map across a bunch of variables.

Unfortunately, adding this function call at the end of the convert_tib function produces the error message Error in eval(call$Y, envir = data) : object 'a' not found. Quite clearly this has to do with the environments in which ATEnocov is called but I just can't figure out how to feed the variable into the function inside the same function.

CodePudding user response:

You don't really need to play around with NSE to get this to work, you can simply do:

library(dplyr)
library(purrr)
library(experiment)

convert_tib <- function(tib, var) {
  d <- tib %>%
    transmute(across(c(all_of(var), c), as.integer)) %>%
    filter(!is.na(.data[[var]]))
  ATEnocov(d[[1]], d[[2]])
}

map(set_names(vars), convert_tib, tib = mytib)

Notice that the above maps over a named vector - this is to give a named list as output because the ATEnocov() function returns its call which when executed this way is rather uninformative. This gives:

$a
$call
ATEnocov(Y = d[[1]], Z = d[[2]])

$Y
[1] 1 2 3 4 5

$Z
[1] 0 1 0 0 1

$match
NULL

$ATE.est
[1] 0.8333333

$ATE.var
[1] 3.027778

attr(,"class")
[1] "ATEnocov"

$b
$call
ATEnocov(Y = d[[1]], Z = d[[2]])

$Y
[1]  6  7  8  9 10

$Z
[1] 0 1 0 1 0

$match
NULL

$ATE.est
[1] 0

$ATE.var
[1] 2.333333

attr(,"class")
[1] "ATEnocov"

If you want it to return the call in a nicer way you can instead use:

convert_tib <- function(tib, var) {
  tib %>% 
    transmute(across(c(all_of(var), c), as.integer)) %>%
    filter(!is.na(.data[[var]])) %>%
    {
      do.call("ATEnocov", list(as.name(var), as.name("c"), data = quote(.)))
    }
}

Which returns the call as:

$a
$call
ATEnocov(Y = a, Z = c, data = .)

...

Or similarly using rlang:

convert_tib <- function(tib, var) {
  tib %>% 
    transmute(across(c(all_of(var), c), as.integer)) %>%
    filter(!is.na(.data[[var]])) %>%
    {
      rlang::inject(ATEnocov(!!sym(var), c, data = .))
    }
}
  • Related