Many of R’s builtin functions have been implemented to accept a variable number
of parameters. This is represented by ...
, and is usually (but not always)
the last item in a function’s call signature. The signature for lapply
is
a good example, and is given by the following:
lapply(X, FUN, ...)
Within the context of lapply, ...
serves as a placeholder for additional
arguments that should be passed to FUN
(as long as they are constant, and
do not vary along with X
).
We can take advantage of variable parameterization in user-defined functions.
This post demonstrates how to write functions that accept a variable number of
arguments, along with a possible use case the leverages the openxlsx
library. To follow along with the examples, you should have a relatively
recent installation of R, in addition to the openxlsx library.
Implementing Variable Argument Functions in R
Our goal will be to create a function that takes a filepath and a variable number of data.frames, and will write each data.frame to a separate worksheet in a common Excel workbook. Here’s a look at the function’s header:
xlsxCompiler = function(xlsxPath, ...)
Where xlsxPath represents the file system location to which the workbook
should be written, and … representing the variable number of data.frames
that can be processed by xlsxCompiler.
Within the context of an executing function in R, the expression
as.list(match.call(expand.dots=TRUE))
returns a list containing the name
of the function as a string in the first position, any keyword parameters and
values in the next position(s), followed by any other variable/optional
parameter names. Using the builtin datasets trees, women and rock, we
demonstrate what calling as.list(match.call(expand.dots=TRUE))
would return:
xlsxCompiler = function(xlsxPath, ...) {
allFuncArgs = as.list(match.call(expand.dots=TRUE))
return(allFuncArgs)
}
> xlsxCompiler(xlsxPath="E:\\Data\\example.xlsx", trees, women, rock)
[[1]]
xlsxCompiler
$xlsxPath
[1] "E:\\Data\\example.xlsx"
[[3]]
trees
[[4]]
women
[[5]]
rock
(I realize that the return
statement doesn’t need to be included, since
the last object referenced within the scope of the function will be returned
to the caller. Feel free to adjust as necessary to suit your preference).
This output matches exactly with what was described above. One thing to note
is that elements 3-5 are the names of the objects passed as optional
parameters, not the objects themselves. In order to capture the objects
(data.frames) that correspond to the optional parameters, we enclose ...
in list
, and assign the result to a local variable within xlsxCompiler.
To demonstrate, we’ll print out the first 5 rows of each data.frame passed
into our function:
xlsxCompiler = function(xlsxPath, ...) {
allFuncArgs = as.list(match.call(expand.dots=TRUE))
varObjArgs = list(...)
return(lapply(varObjArgs, head, n=5))
}
> xlsxCompiler(xlsxPath="E:\\Data\\example.xlsx", trees, women, rock)
[[1]]
Girth Height Volume
1 8.3 70 10.3
2 8.6 65 10.3
3 8.8 63 10.2
4 10.5 72 16.4
5 10.7 81 18.8
[[2]]
height weight
1 58 115
2 59 117
3 60 120
4 61 123
5 62 126
[[3]]
area peri shape perm
1 4990 2791.90 0.0903296 6.3
2 7002 3892.60 0.1486220 6.3
3 7558 3930.66 0.1833120 6.3
4 7352 3869.32 0.1170630 6.3
5 7943 3948.54 0.1224170 17.1
The three data.frame’s correspond to the first five records of trees,
women and rock respectively.
Since we have the names of the optional arguments contained within the
allFuncArgs
variable, and the objects themselves in varObjArgs
, all
that remains to be done is to write the data.frames to a workbook and save
the file to the location specified by xlsxPath. Here’s the full
specification for xlsxCompiler, which incorporates functionality available
in the openxlsx
package:
# ===============================================================
# xlsxCompiler takes a destination path (must end with `.xlsx`) |
# and a variable number of data.frames, and writes them to |
# an Excel workbook in worksheets named identically to the |
# object name in R. |
# ===============================================================
suppressWarnings(suppressPackageStartupMessages(require("openxlsx")))
xlsxCompiler = function(xlsxPath, ...) {
varObjArgs = list(...)
allFuncArgs = as.list(match.call(expand.dots=TRUE))
optFuncNames = as.character(allFuncArgs[3:length(allFuncArgs)])
wb = openxlsx::createWorkbook()
for (i in 1:length(varObjArgs)) {
iterName = optFuncNames[[i]]
iterDF = varObjArgs[[i]]
if (is.data.frame(iterDF)) {
openxlsx::addWorksheet(wb,iterName)
openxlsx::writeData(
wb,iterName,iterDF,xy=c(1,1),colNames=TRUE,
rowNames=FALSE
)
}
}
openxlsx::saveWorkbook(wb,file=xlsxPath,overwrite=TRUE)
}
# Then, call `xlsxCompiler` without assignment =>
> xlsxCompiler(xlsxPath="E:\\Data\\example.xlsx", trees, women, rock)
xlsxCompiler doesn’t return a value or object. Upon successful execution, a
workbook name4d in accordance with xlsxPath will have been created, and it
will contain the data.frames passed into xlsxCompiler as optional arguments.