0

I have created a function which has a reasonably large number of parameters (all vectors of the same length) and I thought it would make life easier to be able to bundle up the parameters in a data frame. I have managed to achieve this using S3 methods, but reading a bit more about S3 methods, I am starting to wonder whether the way I have coded my functions is a bit of an abuse of S3 conventions.

I'd like to know whether what I have done is a bad idea or not. If it is, alternative approaches would be welcome.

Here is a simplified example of what I have done:

myfunc <- function(x, ...) UseMethod("myfunc")

myfunc.default(time, money, age, weight) {
   # a silly calculation
   return(money/(age + weight) - time)
}

myfunc.data.frame <- function(params, ...) {
  names(params) <- tolower(names(params))
  pass.args <- intersect(names(params), names(formals(myfunc.default)))
  res <- do.call(myfunc.default, c(params[pass.args], ...))
  return(res)
}

Then if I had a data frame mydata with column names Money, time, AGE, weight and name then a call like this myfunc(mydata) would pass the relevant data to myfunc.default. It all works well, but is it wise?

seancarmody
  • 6,182
  • 2
  • 34
  • 31
  • Will you ever have another class of object to pass to `myfunc`? In other words, do you see a scenario to use `myfunc.newobject()`? If not, you don't need S3 methods and classes. Also, your `myfunc.data.frame` will not pass `R CMD check`, so at the very least, you haven't used S3 methods *properly*. I suspect you can do exactly what you did above, without using methods at all. – Andrie Aug 02 '12 at 13:05
  • I envisage only two scenarios: passing a data frame `myfunc(mydata)` or passing this individual variables: `myfunc(age=c(20, 40, 25), weight=c(65, 88, 58),...)` etc. I could just have two functions, `myfunc` and `myfunc_df` but thought it would make life easier if they both had the same name. Note that in the real version, all the arguments of the default function have defaults so in practice it's easy to use unless you want to set a lot of the parameters. – seancarmody Aug 02 '12 at 20:48
  • 2
    This feels like a bad idea to me. As I've said, your functions wouldn't pass `R CMD check` since you don't have the same arguments in the generic and the methods. I'd be inclined to use this same idea, but to have a `switch()` or `if()` as the first statement that checks for the input type. Or else at least clean up the generics - perhaps by passing a `list` to your default method, rather than vectors. (PS. You have some clever ideas in that code!) – Andrie Aug 02 '12 at 21:04
  • Thanks Andrie. I did have a lurking feeling that it is a bad idea. Thinking about it, I think I would always be happy naming the arguments when I pass vectors, so perhaps the best idea is to have a single function that looks like this: `myfunc <- function(params=NULL, age=, weight=)` and then have a check that, if supplied, `params` is a data frame. The only thing I give up is being able to call it as `myfunc(c(20, 25, 23))`, i.e. passing a vector without naming it. If `params` is a vector and `age` is not passed, I could even assume that the vector is supposed to be `age`. – seancarmody Aug 03 '12 at 05:46

1 Answers1

0

Thanks for the comments. I have concluded my strategy of using S3 methods here was a bad idea. I have switched to two functions, such as myfunc and myfunc_df. I have also created a helper function for doing the heavy lifting of turning a function with individual arguments into one which accepts a data frame:

df_call <- function(.function, .parameters, .case=tolower, ...) {
  try(names(.parameters) <- match.fun(.case)(names(.parameters)))
  pass.args <- intersect(names(.parameters), names(formals(.function)))
  do.call(.function, c(.parameters[pass.args], list(...)))
}
seancarmody
  • 6,182
  • 2
  • 34
  • 31