Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Default argument in R function (formal argument matched by multiple actual arguments)

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.

like image 471
Stephen Turner Avatar asked May 28 '14 21:05

Stephen Turner


3 Answers

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.

like image 106
joran Avatar answered Sep 30 '22 20:09

joran


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.

like image 30
Arun Avatar answered Sep 30 '22 21:09

Arun


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)
}
like image 42
Matthew Lundberg Avatar answered Sep 30 '22 21:09

Matthew Lundberg