How to extend S3 method from another package without loading the package - r

I am developing a package which has the function forecast.myclass. I want that function to work nicely with forecast package. I.e. when forecast package is loaded the code forecast(object) should call forecast.myclass from my package.
Since I need only generic definition of forecast from the package forecast, and I do not use any other function from the package forecast I am reluctant to include it in the Depends. So I define the generic in my package in the following way:
##'
##' #export
forecast <- function(object,...) UseMethod("forecast")
##' #rdname forecast.midas_r
##' #method forecast midas_r
##' #export
forecast.midas_r <- function(object,newdata=NULL,method=c("static","dynamic"),insample=get_estimation_sample(object),...) {
Now everything works as expected when package forecast is not loaded. But when I load package forecast, then forecast.midas_r is not called, when doing forecast(object) where object is of class midas_r. How should I solve this problem?

I'm not sure there's an easy solution to this. As others have said it's probably easiest to use Depends to get around this, rather than redefining a generic method.
Here's a simple example which works for me. It's largely the same as your solution, but declaring #export means you won't need to manually update the NAMESPACE file.
##' #name mean
##' #export mean.newClass
##'
##' #method mean newClass
##'
##' #title mean for \code{newClass} object
##' #param x A \code{newClass} object
##' #param ... Additional arguments
##'
mean.newClass <- function(x, ...){
stopifnot(class(x)=="newClass")
return(42)
}
Then package.skeleton("newPkg"). Put file mean.R with the above contents in the directory /R of the package.
Ensure you're in the directory 1 level below, then
roxygenize("newPkg", roxygen.dir="newPkg", copy.package=F, unlink.target=F)
Now
library(devtools)
dev_mode(on=TRUE) ### don't want to have to uninstall the package later
install_local("newPkg")
library(newPkg)
x <- c(1,2)
class(x) <- "newClass"
stopifnot(mean(x)==42)
stopifnot(mean(unclass(x))==1.5)
I realize mean is a function in base but I have tested this for modifying generic functions elsewhere to give them a new method, so it should extend to your more general case also.

The problem here is that your definition of the forecast generic is masking the definition from the forecast package, and your method is associated with your generic rather than the forecast package generic; this is just a complicated instance of two packages defining functions of the same name. The solution is to bite the bullet and Depend: on forecast, or when at the command line and both your package and forecast are attached fully resolve the function mypackage::forecast(), or Import: forecast but not make the forecast generic available to the end user except by having them require(forecast) (this might be appropriate if forecast functionality were somehow peripheral to your package, e.g., plotting in 3D when plotting in 2D was sufficient).
For what it's worth, an S4 method in PkgB defined and exported on an imported S4 generic from PkgA implicitly exposes the S4 generic to the user, so the generic is available even if Imports: PkgA is specified in the DESCRIPTION file of PkgB.

One possible solution is to forcefully export forecast.midas_r. This means manually updating NAMESPACE with export(forecast.midas_r) everytime after check("yourpackagename").
What this does is make forecast.midas_r visible for package forecast. If forecast.midas_r is not exported, and exists only in the namespace of the package, then when package forecast is loaded, the generic forecast is overwritten with the identical function from package forecast. So when forecast is invoked on unknown object, R looks up for corresponding methods in package forecast and in general workspace. Since forecast.midas_r is a private method R does not find it and produces an error.
This is not a perfect solution, since you need to manually update NAMESPACE, but it is a solution nevertheless.

This github issue was very helpful for me.
Following the patterns of {sf}:
Don't export your functions in your namespace
Copy the register_s3_method() function definition in a new R script or the same that has your methods
copy the scaffolding for registering all methods and repeat the body for all of the methods you may have in your package
register_all_s3_methods = function() {
register_s3_method("pkg", "function", "class")
}
In a new R script (preferably zzz.R), have an .onLoad() hook containing
.onLoad = function(libname, pkgname) {
register_all_s3_methods()
}
Redocument your package
devtools::load_all()

Related

How to create new method for generics in ggplot2?

I'm trying to create a new guide for ggplot2 and in the process I had to create a new method for the ggplot2 generic guide_train(). In my code I have
# constructor function which returns an object of
# class = c("guide", "colorbar2", "colorbar")
#' #export
guide_colourbar2 <- function(...) {
# things
}
# methods
#' #export
guide_train.colorbar2 <- function(guide, scale) {
# things
}
The problem is that now CMD check complains that there's no documentation for guide_train.colorbar2. I gather this is because roxygen2 exports it as regular function (export(guide_train.colorbar2)). But since this method is internal to ggplot2 workings, it makes no sense to document it.
Creating my own generic dispatch changes my NAMESPACE so that the new method is exported as S3method(guide_train,colorbar2) and solves the CMD check warning, but now the new guide doesn't work. Using it throws
Error in UseMethod("guide_train") : no applicable method for
'guide_train' applied to an object of class "c('guide', 'colorbar2')"
In this vignette it says that "If you want to add a new method to an S3 generic, import it with #importFrom pkg generic." But adding #importFrom ggplot2 guide_train to my function definition makes my package fail to load with error
Error : object ‘guide_train’ is not exported by 'namespace:ggplot2'
This is the first time I'm working with the S3 system, but it seems to me that the correct way would be the #importFrom way and the real problem is that ggplot2 doesn't export the generic. Is that the case or there are other problems with my code?

Function imported from dependency not found, requires library(dependency)

I am trying to create an R package that uses functions from another package (gamlss.tr).
The function I need from the dependency is gamlss.dist::TF (gamlss.dist is loaded alongside gamlss.tr), but it is referenced in my code as simply TF within a call to gamlss.tr::gen.trun.
When I load gamlss.tr manually with library(), this works. However, when I rely on the functions of the dependency automatically being imported by my package through #import, I get an "object not found" error as soon as TF is accessed.
My attempt to be more explicit and reference the function I need as gamlss.dist::TF resulted in a different error ("unexpected '::'").
Any tips on how to use this function in my package would be much appreciated!
The code below reproduces the problem if incorporated into a clean R package (as done in this .zip), built and loaded with document("/path/to/package"):
#' #import gamlss gamlss.tr gamlss.dist
NULL
#' Use GAMLSS
#'
#' Generate a truncated distribution and use it.
#' #export
use_gamlss <- function() {
print("gen.trun():")
gamlss.tr::gen.trun(par=0,family=TF)
#Error in inherits(object, "gamlss.family") : object 'TF' not found
#gamlss.tr::gen.trun(par=0,family=gamlss.dist::TF)
#Error in parse(text = fname) : <text>:1:1: unexpected '::'
y = rTFtr(1000,mu=10,sigma=5, nu=5)
print("trun():")
truncated_dist = gamlss.tr::trun(par=0,family=TF, local=TRUE)
model = gamlss(y~1, family=truncated_dist)
print(model)
}
use_gamlss() will only start working once a user calls library(gamlss.tr).
This is due to bad design of gamlss.tr in particular the trun.x functions (they take character vectors instead of family objects / they evaluate everything in the function environment instead of the calling environment).
To work around this, you have to make sure that gamlss.distr is in the search path of the execution environment of gamlss.tr functions (This is why ## import-ing it in your package does not help: it would need to be #' #import-ed in gamlss.tr).
This can be achieved by adding it to Depends: of your package.
If you want to avoid that attaching your package also attaches gamlss.distr, you could also add the following at the top of use_gamlss:
nsname <-"gamlss.dist"
attname <- paste0("package:", nsname)
if (!(attname %in% search())) {
attachNamespace(nsname)
on.exit(detach(attname, character.only = TRUE))
}
This would temporarily attach gamlss.dist if it is not attached already.
You can read more on namespaces in R in Hadley Wickham's "Advanced R"

Exporting and importing S3 method between packages

Disclaimer: I found the solution to this problem when writing this question. But my question now is "How does it work?"
I am trying to export S3 method from one package (say pkg.from) and import it into another package (say pkg.to). To export the method I use roxygen2 comments.
#' #export
myclass <- function() {
structure(NULL, class = 'myclass')
}
#' #export
print.myclass <- function(x, ...) {
print('NULL with class myclass')
}
File NAMESPACE now contains both the constructor and method.
S3method(print,myclass)
export(myclass)
I believe that means both of them are exported. This supports the fact that I can call both after loading the package.
library(pkg.from)
methods(print)
# ...
# [130] print.myclass*
# ...
print(myclass())
# [1] "NULL with class myclass"
The problem is when I want to import this method into the other package using roxygen2.
#' #importFrom pkg.from print.myclass
During Build this message occurs:
Error : object 'print.myclass' is not exported by 'namespace:pkg.from'
ERROR: lazy loading failed for package 'pkg.to'
If I don't import print.myclass it works even if I don't have package pkg.from loaded. Hadley Wickham writes "you don’t need to do anything special for S3 methods". But I don't think importing a function is "anything special". Now I have more questions then I had when started writing this question.
Where does R stores the methods available for every generic?
How can one solve conflicts when he has installed more packages with the same method for a class?

Roxygen thinks a function of mine is an S3 method, and so breaks upon installation of my package

I'm using roxygen to create my own package. I have a function that's causing a problem:
##' extract.sig.metadata
##' #param foo bar
##' #author me
##' #export
extract.sig.metadata <- function(foo){
# does stuff
}
I've created my package skeleton (with create(my-package) from devtools), and I've used document() to process the roxygen tags. However, when I try to install my package, it fails:
...
* installing help indices
** building package indices
** testing if installed package can be loaded
Error : object 'extract' not found whilst loading namespace 'my-package'
Error: loading failed
Execution halted
I'm pretty sure that roxygen thinks that extract.sig.metadata is an S3 method, i.e. a specialized form of export(), but it's not finding the function export(), and so it's breaking. But this isn't an s3 method, it's just a function called extract.sig.metadata. If I look in the Rd code, the /usage tag looks weird:
\usage{
\method{extract}{sig.metadata}(spec.df, var = "product_name",
ratio.cutoff = 0.001, prob.modifer = 3, frequency.cutoff = NA,
verbose = F, assign.to.global.env = FALSE, use.bigrams = T, clean = T,
ngram.dupe.n.cutoff = 0.1, max.obs = 10000)
}
If I do change the name to extractSigMetadata, the problem is technically fixed, and the .Rd code changes,
\usage{
extractSigMetadata(foo)
}
But I would really like to not have to change the name of my function (there are tens of functions that have the same problem in my package, and they are used in a bunch of scripts - it would be a huge pain to change my naming schema not).
---> Does anyone know how I can tell roxygen that this is just a normal function and not weird s3 method? I'm guessing it has something to do with the #method tag, but I don't know how to use it properly enough to make this work. Thanks!!!
Fixed it!
Using #export extract.sig.metadata instead of #export apparently tells roxygen that extract.sig.metadata is the entire function name, and this fixes the problem. In this particular case, I didn't have a generic extract function, but R.utils (a package that my package did not depend upon but was nevertheless loaded) did have an extract function.
Hope this helps anyone with the same problem in the future. Thanks!
P.S. Hadley suggests better naming practices, which I will attempt to follow in the future.

Importing S3 method from another package

I'm trying to import a S3 method, predict from another package pls. I have a function which uses these predicted values. The problem is, when compiling the package:
Error : object 'predict' is not exported by 'namespace:pls'
I've put together this Gist as a minimal example which highlights my problem and contains the following R file:
#' Test function
#'
#' #importFrom pls predict
#'
#' #export
myfunc <- function(x){
stopifnot(class(x) == "mvr")
predict(x)*2
}
To summarise this as the original (below) is now out-dated and in places erroneous or misleading.
The proximal issue is that there is no function named predict in the pls package; there are some unexported S3 methods for predict but no such predict. So you can't import this. The predict generic lives in the stats package and you'll need to import from there as discussed below.
Your package needs to have Depends: pls in the DESCRIPTION in order for the correct predict method to be available to R. There's nothing in pls that you can specifically import.
You also need to import the predict generic from the stats namespace, so add
#' #importFrom stats predict
as that will import the generic in you packages namespace. You will also want to add Imports: stats to your DESCRIPTION file to indicate that you need the stats package; previously we didn't have to declare dependencies on the set of base packages shipped with R (i.e. the non-Recommended ones that ship with R).
Original
The main issue here is the pls doesn't define a function/method predict. It provides several methods for the predict generic, but not the generic itself.
You need to import the generic from the stats package, if you need it - I'm not sure you do as you aren't creating a function that needs or builds on the generic. At the minimum you'll need
#' #importFrom stats predict
though you may need/want to import the entire stats namespace - depends what your package does beyond the function your are currently working on.
The other issue is that predict.mvr is not exported from the pls namespace
> require(pls)
Loading required package: pls
Attaching package: ‘pls’
The following object(s) are masked from ‘package:stats’:
loadings
> predict.mvr
Error: object 'predict.mvr' not found
> pls::predict.mvr
Error: 'predict.mvr' is not an exported object from 'namespace:pls'
> pls:::predict.mvr
function (object, newdata, ncomp = 1:object$ncomp, comps, type = c("response",
"scores"), na.action = na.pass, ...)
As such you can't just import it. Hence your package needs to have Depends: pls in the DESCRIPTION in order for the correct predict method to be found.

Resources