How to apply succesively the same change to each argument of a function?

Viewed 70

I am trying to figure out how to perform an elasticity analysis to a model I have developed. To do so, I intend to apply a 10% on each parameter of the model and measure the change in the output. I have written a simple function including all the parameter as arguments.

My challenge is that I don't know how to automatize this process to all the arguments with an apply function or a loop.

Let's take a dummy function as an example:

model <- function(a=1, b=1, c=1){
   result <- a+(10*b)+(100*c)
   return(result)
   }

I would like to apply the same change to each parameter (+10% of its value) and produce a run for each iteration. For example, if I had to do it manually it would like this:

output_a <- model(a=1.1)
> 111.1

output_b <- model(b=1.1)
> 111

output_c <- model(c=1.1)
> 121

Ideally, all of the outputs would be in the same dataframe to easily process them in the future. Any thoughts on this? Perhaps an existing post I couldn't find?

2 Answers

Create a grid of the desired values, subset it down to those needed and then Map or mapply over it or in (3) directly generate it.

v <- c(1, 1.1)

# either of these would be ok
g <- expand.grid(a = v, b = v,  c = v)
# g <- do.call("expand.grid", Map(function(x) v, c("a", "b", "c")))

g <- g[rowSums(g != 1) == 1, ]

# either of these would be ok
transform(g, result = mapply("model", g$a, g$b, g$c))
# transform(g, result = do.call("mapply", c("model", g)))

giving:

    a   b   c result
2 1.1 1.0 1.0  111.1
3 1.0 1.1 1.0  112.0
5 1.0 1.0 1.1  121.0

2) There are also a number of list comprehension packages (comprehenr, eList, listcompr) that can be used:

library(listcompr)
gen.data.frame(data.frame(a, b, c, result = model(a, b, c)), 
  a = v, b = v, c = v, (a != 1) + (b != 1) + (c != 1) == 1)

giving:

    a   b   c result
1 1.1 1.0 1.0  111.1
2 1.0 1.1 1.0  112.0
3 1.0 1.0 1.1  121.0

3) diag We can directly create the g matrix using diag:

g <- diag(.1, 3) + 1
colnames(g) <- c("a", "b", "c")
cbind(g, result = apply(g, 1, function(x) model(x[1], x[2], x[3])))

giving this matrix:

       a   b   c result
[1,] 1.1 1.0 1.0  111.1
[2,] 1.0 1.1 1.0  112.0
[3,] 1.0 1.0 1.1  121.0

Changing the parameter names is a bit messy, but you can do the do.call() syntax to build a named list. You can also use formalArgs() to get the names of the arguments of your function

So you could do something like

lapply(formalArgs(model), function(arg) {
  do.call("model", setNames(list(1.1), arg))
})

you could also use a more tidyverse approach with rlang and purrr

library(rlang)
library(purrr)
formalArgs(model) %>% 
  map(~exec(model, !!.x := 1.1))
Related