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.