2

I am trying to update a call for a new function I developed with a new class. The developing is pretty similar to linmod found in Leish's article "Creating R packages".

Inside the function, the call is stored with match.call().

When I try to update the call, as follows:

library(MASS)
fit <- linmod(Hwt~Bwt*Sex, data=cats)
update(fit, subset = -1)

I got the following error message:

Error in eval(expr, envir, enclos) : could not find function "linmod.formula"

The problem seems to be that match.call() saves the full S3 method name (linmod.formula), instead of just the generic function name (linmod), which would work perfectly.

Anyone could help me how to solve this problem?

droubi
  • 93
  • 8
  • I don't understand. It appears like you actually want to update a call to `linmod.formula`. Please provide a reproducible example. – Roland Dec 22 '15 at 10:52
  • Ok, it will be easier if you install linmod package from github: devtools::install_github("lfpdroubi/linmod"). The code is identical to the one found in the article cited above. I've just added roxygen2 comments. Then you can run the code in the question. Maybe this is just a problem with the way roxygen2 manages NAMESPACE, but I am not sure. Thanks in advance. – droubi Dec 22 '15 at 12:52

2 Answers2

0

The easiest way I know for fixing this is exporting the method. For this, you need to add @export linmod.formula. Of course, it is generally not recommended to export methods.

Another option is creating a method for update. The following is a copy of update.default with one additional line:

#' @export
update.linmod <- function (object, formula., ..., evaluate = TRUE) 
{
  if (is.null(call <- getCall(object))) 
    stop("need an object with call component")
  extras <- match.call(expand.dots = FALSE)$...
  #call generic instead of method:
  call[[1]] <- quote(linmod)
  if (!missing(formula.)) 
    call$formula <- update.formula(formula(object), formula.)
  if (length(extras)) {
    existing <- !is.na(match(names(extras), names(call)))
    for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
    if (any(!existing)) {
      call <- c(as.list(call), extras[!existing])
      call <- as.call(call)
    }
  }
  if (evaluate) 
    eval(call, parent.frame())
  else call
}

I dislike both options and would avoid having methods for the linmod function. Your default method seems useless to me. Note how, e.g., lm is not an S3 generic.

PS: update doesn't have a subset parameter.

Roland
  • 127,288
  • 10
  • 191
  • 288
  • It seems to work exporting the method, as you said. The problem was that I was adding the export comment before the definition of the method, not export linmod.formula. I was doing it based in this [post] (http://stackoverflow.com/questions/7198758/roxygen2-how-to-properly-document-s3-methods). I agree that the defaul method seems useless. But not only. The linmod function itself is useless. This is just a didactic package designed to teach newbies like me how to make a package. About the subset parameter, it was passed to update to be then passed to linmod. It didn´t work anyway. :) Thank you – droubi Dec 22 '15 at 14:32
0

Since this hasn't been mentioned here yet, and it is the approach explicitly recommended in ?update: write a method for getCall. From ?update:

“Extracting the call” in update() and similar functions uses getCall() which itself is a (S3) generic function with a default method that simply gets x$call. Because of this, update() will often work (via its default method) on new model classes, either automatically, or by providing a simple getCall() method for that class.

So, in your package, if you have:

#' @export
f <- function(x) {
  UseMethod("f")
}

#' @export
f.bar <- function(x) {
  structure(list(x = x, call = match.call()), class = "fbar")
}

#' @export
#' @importFrom stats getCall
getCall.fbar <- function(x) {
  x$call[[1L]] <- quote(f) # replacing `f.bar`
  x$call
}

Then, in your script, you could do:

x1 <- structure(1, class = "bar")
x2 <- structure(2, class = "bar")

fx1 <- f(x = x1)
fx2 <- update(fx1, x = x2)

fx1
# $x
# [1] 1
# attr(,"class")
# [1] "bar"
#
# $call
# f.bar(x = x1)
#
# attr(,"class")
# [1] "fbar"

fx2
# $x
# [1] 2
# attr(,"class")
# [1] "bar"
# 
# $call
# f.bar(x = x2)
# 
# attr(,"class")
# [1] "fbar"
Mikael Jagan
  • 9,012
  • 2
  • 17
  • 48