2

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).

BGranato
  • 129
  • 1
  • 10
  • 1
    You can't really do this. Values you pass into a function are evaluated in the calling environment but default parameter values are evaluated in the function scope. Maybe consider passing a function instead to `g` rather than an expression. – MrFlick Feb 18 '19 at 21:52

3 Answers3

4

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)
G. Grothendieck
  • 254,981
  • 17
  • 203
  • 341
2

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 })
Marius
  • 58,213
  • 16
  • 107
  • 105
  • One way to implement this is to use `fn$` which lets the `g` function be defined as a formula. Using `myFunc` shown in this answer we can write the last line of code above as: `library(gsubfn); fn$myFunc(5, g = ~ s - a)` . The RHS of the formula is taken as the body and the arguments are assumed to be the free variables in the formula in the order encountered (or the formula can be expressed as `s + a ~ s - a` to explicitly specify the order of the arguments. – G. Grothendieck Feb 19 '19 at 14:12
2

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))
}
Nick Kennedy
  • 12,510
  • 2
  • 30
  • 52
  • I ended up going with @Marius reply because it fit my code better, but I definitely try this out next time! Thank you – BGranato Feb 19 '19 at 00:36