Reputation: 169
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
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
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
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