8

Simple question, I hope. I want to write a plotting function that has a default value for the y-axis label if the user doesn't specify. I'd also like to allow the ... argument for other plotting parameters, and allow the user to set ylab manually. But I can't figure out how to do this.

# simple scatterplot function with a default ylab
scatter <- function(x,y, ...) {
    plot(x, y, ylab="Default y-axis label", ...)
}

# generate data
x <- rnorm(100)
y <- x+rnorm(100)

# use the default
scatter(x,y)

# here I want to use my own label, but I get an error!
scatter(x, y, ylab="New y-axis label")

The error I get is:

Error in plot.default(x, y, ylab = "Default y-axis label", ...) : 
  formal argument "ylab" matched by multiple actual arguments 

I understand the problem, but I don't know the best way to fix it. Thanks for the help!

EDIT: I realize I can do something like

scatter <- function(x,y,ylab = "Default y-axis label", ...) {
    plot(x, y, ylab= ylab, ...)
}

...but if I'm writing a package to submit to CRAN, and I have lots of default options I'd like to fiddle with, I don't want to have to document all these standard plotting arguments because they're used in my function definition.

Stephen Turner
  • 2,574
  • 8
  • 31
  • 44

3 Answers3

11

Try doing this instead:

scatter <- function(x,y,ylab = "Default y-axis label", ...) {
    plot(x, y, ylab= ylab, ...)
}

Expanding slightly on Arun's answer, this is a sketch of one route to take if you have many arguments:

def_args <- list(ylab = "Default Label",xlab = "Default Label")

scatter <- function(x,y, ...) {
    cl <- as.list(match.call())[-1L]
    do.call("plot",c(cl,def_args[!names(def_args) %in% names(cl)]))
}

Some thought would be needed to decide how you want to handle partial matching of arguments (if at all). e.g. perhaps something like this:

scatter <- function(x,y, ...) {
    cl <- as.list(match.call())[-1L]
    names(cl) <- match.arg(names(cl),
                           names(formals(plot.default)),several.ok = TRUE)
    do.call("plot",c(cl,def_args[!names(def_args) %in% names(cl)]))
}

would handle partial matching of arguments.

joran
  • 169,992
  • 32
  • 429
  • 468
  • That works, but if I'm writing a package, it's yet another argument I have to document unnecessarily. I've got lots of these default plotting parameters I want to fiddle with but don't want to document again. – Stephen Turner May 28 '14 at 21:31
  • @StephenTurner It would take me a few minutes to work up an example, but one option would be to not provide an explicit default at all, but rather to pack everything in `...` and then inspect `...` inside `scatter` and then depending on what the user passed, construct an appropriate call to `plot`. – joran May 28 '14 at 21:35
  • @StephenTurner i.e. _exactly_ what Arun just provided. – joran May 28 '14 at 21:36
  • Thanks - will take a look at both of these solutions. You're right, the trick will be handling multiple permutations of specified and "default" options. – Stephen Turner May 29 '14 at 12:42
  • Thanks. Using `do.call` with the argument list provided here solved the problem perfectly. Seems like there'd be a less ick way of doing this, but this works fine. – Stephen Turner May 29 '14 at 14:18
5

One way using match.call to check if ylab has been specified as an argument:

scatter <- function(x,y, ...) {
    mcall = as.list(match.call())[-1L]
    if (!"ylab" %in% names(mcall))
        plot(x, y, ylab="Default y-axis label", ...)
    else plot(x, y, ...)
}

As mentioned under comment list(...) is a nicer way to get just the dots argument expanded than having to get all the formal arguments with match.call.

You might also try using pmatch instead of %in% for partial matching of arguments.

Arun
  • 116,683
  • 26
  • 284
  • 387
  • 1
    Isn't `...` more easily captured as `args <- list(...)` inside the fxn? – sckott May 28 '14 at 21:37
  • 1
    Exactly. The trick in the OP's situation is probably going to be scaling this to work with a large number of arguments. I was thinking something along the lines of having a set of default arguments in a list ready to go, and then taking a "union" with `...` and then tossing it to `do.call` or something. – joran May 28 '14 at 21:40
3

I use a function to build an argument list. In my case, I do not care about partially matching argument names, which is good because this won't support it.

# Create a list of input arguments.
# Allow arguments to be specified multiple times, first definition wins.
# The resulting list is intended to be passed to do.call().

make.args <- function(..., PRE.ARGS=list(), POST.ARGS=list()) {
  a <- list()
  l <- c(PRE.ARGS, list(...), POST.ARGS)
  for (name in unique(names(l))) {
    a[[name]] <- l[[name]] # First occurrence will be found.
  }
  return(a)
}

An example of its use:

plot.rate <- function(col, cond=NULL, ...) {
    col <- paste(col, collapse=' + ')
    f <- paste(col, '~ Rate')
    if (!is.null(cond)) {
        cond <- paste(cond, collapse=' + ')
        f <- paste(f, cond, sep='|')
    }

    arg.list <- make.args(...
                    , x = as.formula(f)
                    , main=col
                    , grid=TRUE
                    , scales=list(x=list(alternating=1)    # bottom(/left)
                                , y=list(alternating=3)) # both
                                , xlab='x RTM'
    )

    do.call(xyplot, arg.list)
}
Matthew Lundberg
  • 42,009
  • 6
  • 90
  • 112