1

There are number of good (1 2 3) StackOverflow questions on the use of an ellipsis ("dots") in R. However, I haven't met one that addresses my particular use case yet. I am struggling to write a compatibility wrapper for a function that changes the name of an argument from the original function call to the underlying function call. For example, one might want to call newMean(c(1:10,1000,NA),.2,rmNA=TRUE) rather than mean(c(1:10,1000,NA),.2,na.rm=TRUE). We might even want to write our wrapper in such a way that we don't kill the S3 calls for mean by assuming all calls to newMean are meant to be dispatched for mean.default. The apparent path forward is something like (in pseudocode):

newMean <- function(x,...) {
  dots <- list(...)
  names(dots)[names(dots)=="rmNA"] <- "na.rm"
  x.list <- list(x=x)
  dots <- c(x.list,dots)
  do.call(base::mean,dots)
}

This leaves us with a value in dots like:

dots <- structure(list(x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000, NA), 
    0.2, na.rm = TRUE), .Names = c("x", "", "na.rm"))

So far so good. But something isn't quite the way I might otherwise expect it. Consider the command newMean(c(1:10,1000,NA),.2,TRUE) versus base::mean(c(1:10,1000,NA),.2,TRUE). When we call base::mean R then calls mean.default because getS3method("base::mean",class(x)) for the x in the call is numeric and has no match. match.call(mean.default) called while debugging newMean shows that the (unnamed) second and third argument are, of course, assigned in order to the three arguments before the ellipsis in mean.default. In contrast, do.call seems to be ignoring the unnamed arguments (maybe treating them as being part of the ellipsis in the call of mean.default?).

I've tried identifying the S3 method to be used using getS3method then using formals to dig into and identify the number of formal arguments to the particular S3 method being dispatched, but I haven't been able to generate a way to make a call to mean::base that flexibly respects the number of formals present in the method to be called and matches them appropriately.

Without deparsing a matched call from newMean to base:mean (e.g. deparse(match.call(base::mean))) and manually subbing argument names, is there a way to fix the behavior of newMean such that trim and na.rm are appropriately matched? The reason I don't want to string manipulate the substitution of the argument names is I that I can imagine cases (outside of this toy example) where values in the call might be partially matched to argument names.

Community
  • 1
  • 1
russellpierce
  • 4,583
  • 2
  • 32
  • 44
  • I lost the train of thought somewhere along the way but then noticed that you have both "rm.na" and "na.rm", so I'm guessing you didn't use a print-out of value in what you presumed that output would be. The `match.arg` function would allow partial matches. (I also found the use of the word "ellipses" confusing since I believe the correct term for the three dots together is just "ellipsis".) – IRTFM Nov 27 '13 at 17:53
  • I think theproblem is that you are giving the arguments to newMean as unnamed when you deliberately wrote newMean with only one named argument. So .... there are no names for the dots-list. Trying to match to them is therefore doomed. If you instead call with `newMean(c(1:10,1000,NA), trim=.2, rmNA=TRUE)` you would be able to use `match.args` . – IRTFM Nov 27 '13 at 18:15
  • @DWin: You are right about rm.na, I fixed that typo so that it is the na.rm that I love to hate. I also have corrected ellipses to ellipsis. I know match.args would work with named arguments... that is exactly the problem. :/ – russellpierce Nov 27 '13 at 22:00

2 Answers2

2

One approach is to modify the call to newMean() and change it to a call to base::mean():

newMean <- function(...) {
  call <- match.call()
  call[[1]] <- quote(base::mean)

  rmNA <- names(call) == "rmNA"
  names(call)[rmNA] <- "na.rm"

  eval(call, parent.frame())
}
newMean(c(1:10, NA), rmNA = T)

But this is uniformly worse than the simple:

newMean <- function(x, trim = 0, rmNA = FALSE, ...) {
  mean(x, trim = trim, na.rm = rmNA, ...)
}
hadley
  • 102,019
  • 32
  • 183
  • 245
  • Actually I prefer the first over the second as a general answer to my question because the second has to assume which method of mean is doing to be dispatched on x. Are there any other dangers of the first that I should be aware of? Or did you just dislike it on the grounds of parsimony? I had no idea the call was organized (and named) like that; very useful. – russellpierce Nov 27 '13 at 22:08
  • I think you're confused about how S3 dispatch works - both of those functions will dispatch in the same way to the same method of `mean` – hadley Nov 27 '13 at 22:11
  • Except won't the second version of newMean you wrote call mean, and if the mean selected by the class of x is not mean.default but instead some other mean with arguments list like... mean(x, blah=10, orange="purple",...) dispatch with the first two arguments of ... having the specific names of trim and na.rm and then fail to match them to blah and orange leaving those values as default rather than taking the 2nd and 3rd arguments from the original call to newMean? – russellpierce Nov 27 '13 at 22:25
  • @rpierce no, the arguments in ... will be passed on the same way in both cases – hadley Nov 29 '13 at 00:06
  • I see there are some conceptual issues with how I phrased my question. It is true that the arguments in ... are passed the same way in both cases. What is different is what is included in ... between the 1st and 2nd version of newMean. This matters quite a bit for the handling of the 2nd and 3rd arguments if they are unnamed. – russellpierce Nov 29 '13 at 05:23
  • Consider: x <- c(1:10,1000,NA); class(x) <- "nope"; mean.nope <- function(x, blah=10, orange="purple",...) {print(match.call());cat(blah,orange,"\n");if (blah==10) return(TRUE)} else {return(FALSE)}}; newMean(x,.2,10) #this returns different values depending on the version of newMean. – russellpierce Nov 29 '13 at 05:24
0

As I indicated I'm not exactly sure what your goal is, but if all you wanted was to convert an unnamed argument list to one that you could pass to mean.default, then it would be as simple as:

newMean <- function(x,...) {
  dots <- list(...)
  mean(x, trim = dots[[1]], na.rm = dots[[2]] )
}

The utter simplicity of this makes me think I'm not understanding the problem or error you are encountering, but this demonstrates matching on names when they do exist:

newMean.partial <- function(x,...) {
   dots <- list(...)
   names(dots)[names(dots)=="rmNA"] <- "na.rm"
   x.list <- list(x=x)
   dots <- c(x.list,dots)
  print(dput(dots))
 }
 newMean.partial(c(1:10,1000,NA),.2,rmNA=TRUE)
# structure(list(x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1000, NA), 
#     0.2, na.rm = TRUE), .Names = c("x", "", "na.rm"))
IRTFM
  • 258,963
  • 21
  • 364
  • 487