2

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.

Tea Tree
  • 882
  • 11
  • 26

1 Answers1

3

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 = .))
    }
}
Ritchie Sacramento
  • 29,890
  • 4
  • 48
  • 56
  • When I use the first approach and call ```map(set_names(vars), convert_tib, tib = mytib)```, I get the error ```Error in set_names(vars) : 1 argument passed to 'names<-' which requires 2```. Why does this happen? – Tea Tree Mar 28 '22 at 13:18
  • Sorry, I just realized that there were other set_names() functions loaded which masked purrr::set_names(). Calling it explicitly did the trick. – Tea Tree Mar 28 '22 at 13:24
  • The second and third methods work great since they do not assume that the variables used in ATEnocov are in columns 1 and 2 and instead call them by name. – Tea Tree Mar 28 '22 at 15:44
  • That's not correct - these are determined by argument order - if you want to be explicit (and you probably should be) you can add the argument names to the call, e.g. `do.call("ATEnocov", list(Z = as.name("c"), Y = as.name(var), data = quote(.)))`. – Ritchie Sacramento Mar 29 '22 at 05:40