Home > R > Preventing argument use in R

Preventing argument use in R

It sounds silly, but sometimes you don’t want to let people use some arguments of a function. The canonical example is write.csv. The function is effectively a wrapper to write.table, but using “,” as the separator and “.” as the decimal.

We could implement the function in a really simple way, as

simple.write.csv <- function(...) write.table(sep = ",", dec = ".", ...)

Under most circumstances, this implementation works as expected. The problem occurs when we pass the sep or dec arguments into the function; in this case we get a rather unhelpful error message.

Error in write.table(sep = ",", dec = ".", ...) :
+++formal argument "sep" matched by multiple actual arguments

To get round this, the real implementation of write.csv is a little more complex. The basic idea behind it is this:

“Take a look at the call to the function write.csv. Warn the user if any forbidden arguments were passed in, then replace them with specified values. Then pass these on to write.table.”

Let’s take a look at the code.


write.csv <- function (...)
{
+++Call <- match.call(expand.dots = TRUE)
+++for (argname in c("append", "col.names", "sep", "dec", "qmethod"))
++++++if (!is.null(Call[[argname]]))
+++++++++warning(gettextf("attempt to set '%s' ignored", argname), domain = NA)
+++rn <- eval.parent(Call$row.names)
+++Call$append <- NULL
+++Call$col.names <- if (is.logical(rn) && !rn) TRUE else NA
+++Call$sep <- ","
+++Call$dec <- "."
+++Call$qmethod <- "double"
+++Call[[1L]] <- as.name("write.table")
+++eval.parent(Call)
}

Understanding this requires some technical knowledge of the R language. When you type things at the R command prompt and hit Enter, two things happen. Those characters are turned into a call, and then they are evaluated to give you your answer. Effectively, what happens is

eval(quote(the stuff you typed))

You can examine the contents of a call by converting it to be a list, for example


> as.list(quote(2+3))[[1]]
`+`

[[2]]
[1] 2

[[3]]
[1] 3

The first element of the list gives the function being called, and the rest of the elements are the arguments to pass in to that function. Notice that even ‘+’ is a function (and so is ‘<-’).

If you want to know more about this, then take a look at section 6.1 of the R Language definition. Expressions, which are, more or less, lists of calls are discussed in section 6.4.

match.call is like calling quote(the stuff you typed when you called this function)

Returning to the example, let’s have a sample call to write.csv.


dfr <- data.frame(x = 1:5, y = runif(5))
write.csv(dfr, file = "test.csv", sep = "!")

The crux of understanding write.csv is in the variable Call, assigned on the first line of the function.


> as.list(Call)[[1]]
write.csv

[[2]]
dfr

$file
[1] "test.csv"

$sep
[1] "!"

The rest of the function involves manipulating this object. The element sep is replaced with a comma (with a warning) and some additional arguments are set. The function that is called is then swapped for write.table, and finally this call is evaluated.

Deep understanding of this can get a bit mind-melting but if you want to use this principle in your own functions, you can more or less copy and paste from write.csv.

About these ads
Tags: ,
  1. Andrej Spiess
    21st September, 2010 at 16:37 pm | #1

    How about this?

    x <- 1:7

    write.csv <- function(x, …)
    {
    dots <- list(…)
    do.call(write.table, modifyList(list(x, sep = ",", dec = "."), dots))
    }

    ####
    write.csv(x)
    write.csv(x, sep = "\t")

  2. 21st September, 2010 at 17:53 pm | #2

    @Andrej: I like the use of do.call. It is perhaps easier to understand than using match.call and eval.parent. Your version of the code doesn’t seem to suppress arguments though. A rewrite of write.csv (including warnings) using do.call would be

    write.csv <- function(…)
    {
    dots <- list(…)
    for(argname in c("append", "col.names", "sep", "dec", "qmethod"))
    {
    if(!is.null(dots[[argname]]))
    {
    warning(gettextf("attempt to set '%s' ignored", argname), domain = NA)
    }
    }
    dots$append <- NULL
    dots$col.names <- if (exists("rn") && is.logical(dots$rn) && !dots$rn) TRUE else NA
    dots$sep <- ","
    dots$dec <- "."
    dots$qmethod <- "double"
    do.call(write.table, dots)
    }

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 160 other followers

%d bloggers like this: