Search code examples
rfunctionevaluationexpression-evaluation

How to pass arguments into a function as formula arguments like y ~ x


I am trying to write a function that calls another function that requires formula arguments. However, I'm not able to pass the y ~ x arguments to the inner function.

Without the wrapping function it works:

test.df <- data.frame("test.x" = c(1,1,1,2,2), "test.y" = 1:5)
my.result <-lm(data = test.df, formula = test.y ~ test.x)

But I want this to work:

example.function <- function(my.data, my.y){
  my.result <- lm(data = my.data, formula = my.y ~ test.x) 
  return(my.result)
}

example.function(my.data = test.df, my.y = test.y) 
# Error in eval(predvars, data, env) : object 'test.y' not found

example.function(my.data = test.df, my.y = "test.y") 
# Error in model.frame.default(formula = my.y ~ test.x, data = my.data, :
# variable lengths differ (found for 'test.x')

I tried to use {{}} but this also doesn't work:

example.function <- function(my.data, my.y){
  my.result <- lm(data = my.data, formula = {{my.y}} ~ test.x) 
  return(my.result)
}

example.function(my.data = test.df, my.y = test.y)
# Error in eval(predvars, data, env) : object 'test.y' not found

example.function(my.data = test.df, my.y = "test.y")
# Error in model.frame.default(formula = { :
# variable lengths differ (found for 'test.x')

And I also tried to use enquo() and !! but this doesn't work either:

example.function <- function(my.data, my.y){
  enquo(my.y)
  my.result <- lm(data = my.data, formula = !! my.y ~ test.x) 
  return(my.result)
}

example.function(my.data = test.df, my.y = test.y)
# Error in eval(predvars, data, env) : object 'test.y' not found

example.function(my.data = test.df, my.y = "test.y")
# Error in !my.y : invalid argument type

Thank you for any help understanding this!


Solution

  • This gives what was asked for.

    On the other hand you might consider not using non-standard evaluation (NSE) and instead pass a character string for my.y since then it becomes easy to pass a variable holding the variable name and not just hard coding the call. Also the code becomes shorter. We show that as example.function2 at the end.

    example.function <- function(my.data, my.y) {
      my.y <- deparse(substitute(my.y))
      fo <- reformulate("test.x", my.y)
      do.call("lm", list(fo, substitute (my.data)))
    }
    
    example.function(my.data = test.df, my.y = test.y) 
    

    giving

    Call:
    lm(formula = test.y ~ test.x, data = test.df)
    
    Coefficients:
    (Intercept)       test.x  
           -0.5          2.5  
    

    Same but without NSE. Output is the same.

    example.function2 <- function(my.data, my.y) {
      fo <- reformulate("test.x", my.y)
      do.call("lm", list(fo, substitute (my.data)))
    }
    
    example.function2(my.data = test.df, my.y = "test.y")