9.6 A Note on Ellipses

The functions of the previous section contained a mysterious ... argument in their definitions. This is known in R as the ellipsis argument, and it signals the possibility that one or more additional arguments may be supplied when the function is actually called.

The following function illustrates the operation of the ellipsis argument:

ellipisDemo <- function(...) {
  cat("I got the following arguments:\n\n")
  print(list(...))
}
ellipisDemo(x = 3, y = "cat", z = FALSE)
## I got the following arguments:
## 
## $x
## [1] 3
## 
## $y
## [1] "cat"
## 
## $z
## [1] FALSE

At this point in our study of R, ... is useful in two ways.

9.6.1 Use #1: Passing Additional Arguments to Functions “Inside”

Look again at the code for the function means2():

means2 <- function(vecs = list(), ...) {
  n <- length(vecs)
  if ( n == 0 ) {
    return(cat("Need some vectors to work with!"))
  }
  results <- numeric(n)
  for ( i in 1:n ) {
    results[i] <- mean(vecs[[i]], ...)
  }
  results
}

We plan to take the mean of some vectors and therefore the mean() function will be used in the body of means2(). However we would like the user to be able to decide how mean() deals with NA-values. When we include the ellipsis argument in the definition of means2() we have the option to pass its contents into mean(), and we exercise that option in the line:

results[i] <- mean(vecs[[i]], ...)

Now we can see what happens in the call:

means2(vecs = list(vec1, vec2, vec3), na.rm = TRUE)

The ellipsis argument will consist of the argument na.rm = TRUE, hence the call to mean() inside the loop is equivalent to:

results[i] <- mean(vecs[[i]], na.rm = TRUE)

Consider, on the other hand, the call:

means2(vecs = list(vec1, vec2, vec3))

Now the ellipsis is empty. In this case the code in the loop will be equivalent to:

means2(vecs = list(vec1, vec2, vec3))
## [1] 3.0 5.5  NA

As a result, mean() will use the default value of na.rm, which is FALSE. For any input-vector having NA-values, the mean will be computed as NA.

9.6.2 Use #2: Permitting Any Number of Arguments

Another application of the ellipsis argument is in the writing of functions where the number of “primary” arguments is not determined in advance.

We have seen a few R-functions that can deal with any number of arguments. cat() is an example:

cat("argument one,", "argument two,", "and as many more as you like!")
## argument one, argument two, and as many more as you like!

With the ellipsis argument we can do this sort of thing ourselves. For example, here is a function that takes any number of vectors as arguments and determines whether the vectors are all of the same length:

sameLength <- function(...) {
  vecs <- list(...)
  numVecs <- length(vecs)
  if ( numVecs <= 1 ) {
    return(cat("Need two or more vectors."))
  }
  allSame <- TRUE
  len <- length(vecs[[1]])
  for ( i in 2:numVecs ) {
    if ( length(vecs[[i]]) != len ) {
      allSame <- FALSE
      break
    }
  }
  allSame 
}

We can give this function two or more vectors, as follows:

vec1 <- 1:3
vec2 <- 1:4
vec3 <- 1:3
sameLength(vec1, vec2, vec3)
## [1] FALSE