Preventing argument use in R
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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
.
Tagged: calls, r
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.