Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass arguments in nested function to update default arguments

Tags:

arguments

r

I have nested functions and wish to pass arguments to the deepest function. That deepest function will already have default arguments, so I will be updating those argument values.

My mwe is using plot(), but in reality I'm working with png(), with default height and width arguments.

Any suggestions?

f1 <- function(...){ f2(...)}

f2 <- function(...){ f3(...)}

f3 <- function(...){ plot(xlab="hello1", ...)}

#this works
f1(x=1:10,y=rnorm(10),type='b')

# I want to update the default xlab value, but it fails:
f1(x=1:10,y=rnorm(10),type='b', xlab='hello2')
like image 250
mfolkes Avatar asked Feb 23 '16 20:02

mfolkes


2 Answers

In your f3(), "hello1" is not a default value for xlab in the list of function's formal arguments. It is instead the supplied value in the function body, so there's no way to override it:

f3 <- function(...){ plot(xlab="hello1", ...)}

I suspect you meant instead to do something like this.

f1 <- function(...){ f2(...)}
f2 <- function(...){ f3(...)}
f3 <- function(..., xlab="hello1") plot(..., xlab=xlab)

## Then check that it works
par(mfcol=c(1,2))
f1(x=1:10,y=rnorm(10),type='b')
f1(x=1:10,y=rnorm(10),type='b', xlab='hello2')

enter image description here

(Do notice that the formal argument xlab must follow the ... argument here, so that it can only be matched exactly (and not by partial matching). Otherwise, in the absence of an argument named xlab, it'll get matched by an argument named x, potentially (and actually here) causing you a lot of grief.)

like image 128
Josh O'Brien Avatar answered Oct 30 '22 09:10

Josh O'Brien


My usual approach for modifying arguments in ... is as follows:

f1 = function(...) {
  dots = list(...)
  if (!('ylab' %in% names(dots))) {
    dots$ylab = 'hello'
  }
  do.call(plot, dots)
}
# check results 
f1(x = 1:10, y = rnorm(10)) 
f1(x = 1:10, y = rnorm(10), ylab = 'hi') 

What happens here is that ... is captured in a list called dots. Next, R checks if this list dots contains any information about ylab. If there is no information, we set it to a specified value. If there is information, we do nothing. Last, do.call(a, b) is a function that basically stands voor execute function a with arguments b.

edit

This works better with multiple default arguments (and probably also better in general).

f1 = function(...) {
  # capture ... in a list
  dots = list(...)
  # default arguments with their values
  def.vals = list(bty = 'n', xlab = 'hello', las = 1)
  # find elements in dots by names of def.vals. store those that are NULL
  ind = unlist(lapply(dots[names(def.vals)], is.null))
  # fill empty elements with default values 
  dots[names(def.vals)[ind]] = def.vals[ind]
  # do plot
  do.call(plot, dots)
}

f1(x = 1:10, y = rnorm(10), ylab = 'hi', bty = 'l') 
like image 4
Vandenman Avatar answered Oct 30 '22 08:10

Vandenman