0

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!

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
rrr
  • 1,914
  • 2
  • 21
  • 24

1 Answers1

3

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") 
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
  • I was trying to do the NSE because later I pass the same arguments into a ggplot call. Thanks for explaining both ways! I have one question about the NSE way- why use `do.call` and `substitute(my.data)`? It also works for me as `my.result <- lm(data = my.data, formula = fo)` – rrr Feb 11 '23 at 19:46
  • The Call: line in the output won't come out right if you don't do that. In ggplot2 you can do this to avoid NSE: `ggplot(test.df, aes(.data[["test.x"]], .data[["test.y"]])) + geom_point()` – G. Grothendieck Feb 11 '23 at 19:55