BGranato
BGranato

Reputation: 169

Function argument that depends on default arguments

I'm trying to write a function that has default arguments in R. The last argument is telling me how the user would like variable 'g' to be calculated. The default is "s + a" (the sum of the two previous arguments), but in principle it could be specified by any function (e.g. "s - a" or "s*a"...).

myFunc <- function(n, 
                   s = rbernoulli(n, p = 0.5), 
                   a = rnorm(n,sd = 2),
                   g = s + a){
      data.frame(s = factor(s),
                 a = a,
                 g = as.numeric(g>0))
}

This works fine if I call the function itself:

myFunc(5)

To specify how I want 'g' to be calculated, I would like to do this:

myFunc(n = 5, g = a - s) (I)

or

myFunc(n = 5, a = ., s = ., g = a - s) (II)

It seems (I) will cause R to look for variables s/a in the workspace, which is not what I want. And (II) doesn't exist, but it would be my way of saying "use the default calculation for it".

I tried specifying my function with NULL, but that didn't work either. Please note that I'd like to be able to use 'g' within the function after I have its value (so I can't substitute it by a function, for example).

Upvotes: 2

Views: 851

Answers (3)

Nick Kennedy
Nick Kennedy

Reputation: 12640

This can be done using non-standard evaluation. There are a number of ways this could be implemented. I now mostly use quosures and rlang::eval_tidy. Here's an implementation of your function using this:

library(purrr)
library(rlang)
myFunc <- function(
  n, 
  s = rbernoulli(n, p = 0.5), 
  a = rnorm(n, sd = 2),
  g = s + a) {
  if (!missing(g)) {
    g <- eval_tidy(enquo(g), list(s = s, a = a))
  }
  data.frame(s = factor(s),
             a = a,
             g = as.numeric(g>0))
}

This will work using your suggested example myFunc(n = 5, g = a - s). When no g argument is supplied, it defaults to the standard r functionality of evaluating the default expression in the context of the other parameters.

Note also this works with quasiquotation, so you can do something like this:

my_expr <- expr(a - s)
myFunc(n = 5, g = !!my_expr)

There are a couple of great chapters on non-standard evaluation in Hadley Wickham's Advanced R.

Using base R only (except for purrrr:bernoulli which you supplied):

library(purrr)
myFunc <- function(
  n, 
  s = rbernoulli(n, p = 0.5), 
  a = rnorm(n, sd = 2),
  g = s + a) {
  if (!missing(g)) {
    g <- eval(substitute(g), list(s = s, a = a))
  }
  data.frame(s = factor(s),
             a = a,
             g = as.numeric(g>0))
}

Upvotes: 2

G. Grothendieck
G. Grothendieck

Reputation: 269381

The problem is that g is evaluated in the calling environment, not in the environment within myFunc. You could add an argument that specifies the environment to evaluate g in and use a default of environment() so that it defaults to the environment within myFunc2.

myFunc2 <- function(n, 
                   s = rbernoulli(n, p = 0.5), 
                   a = rnorm(n,sd = 2),
                   g,
                   envir = environment()) {
      g <- if (missing(g)) s + a else eval(substitute(g), envir)
      data.frame(s = factor(s),
                 a = a,
                 g = as.numeric(g>0))
}
myFunc2(n = 5, g = s + a)

Upvotes: 4

Marius
Marius

Reputation: 60060

I think the best way to do this is make g a function that accepts two arguments, s and a. Then you can pass a different function when needed:

myFunc <- function(n,
                   s = purrr::rbernoulli(n, p = 0.5),
                   a = rnorm(n, sd = 2),
                   g = function(s, a) { s + a }) {
    g_val = g(s, a)
    data.frame(s = factor(s),
               a = a,
               g = as.numeric(g_val > 0))
}

myFunc(5)
myFunc(5, g = function(s, a) { s - a })

Upvotes: 2

Related Questions