Exporting and importing S3 method between packages - r

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?

Related

R package data not available when importing in another package

I have one package "testing" with a data object "test_data" saved in data folder under file name "test_data.RData".
testing contains one function hello() that uses this data object
#' hello
#'
#' #return Prints hello "your_name"
#' #export
#'
#' #examples
#' hello()
hello <- function(your_name = "") {
print(paste("test_data has", nrow(test_data), "rows"))
print(sprintf("Hello %s!", your_name))
}
the following code works fine:
require(testing)
testing::hello()
[1] "test_data has 32 rows"
[1] "Hello !"
but this fails:
testing::hello()
Error in nrow(test_data) : object 'test_data' not found
Actually I do not use it directly but in another package testingtop that imports this function:
#' Title
#'
#' #export
#' #importFrom testing hello
hello2 <- function(){
hello()
}
I have testing in the Imports section of DESCRIPTION and this fails.
require(testingtop)
testingtop::hello2()
Error in nrow(test_data) : object 'test_data' not found
If I put it in Depends it works if I load the package with library()
otherwise it still fails:
> library(testingtop)
Loading required package: testing
> testingtop::hello2()
[1] "test_data has 32 rows"
[1] "Hello !"
Restarting R session...
> testingtop::hello2()
Error in nrow(test_data) : object 'test_data' not found
if it was a function instead of a data object Imports would be fine, why is it different with a data object and I need to load the imported package? Did I miss something? And is it related to LazyData and LazyLoad ?
Probably a duplicate of this question
SO I think I've found the solution from the doc of the data function ?data
Use of data within a function without an envir argument has the almost always undesirable side-effect of putting an object in the user's workspace (and indeed, of replacing any object of that name already there). It would almost always be better to put the object in the current evaluation environment by data(..., envir = environment()). However, two alternatives are usually preferable, both described in the ‘Writing R Extensions’ manual.
For sets of data, set up a package to use lazy-loading of data.
For objects which are system data, for example lookup tables used in calculations within the function, use a file ‘R/sysdata.rda’ in the package sources or create the objects by R code at package installation time.
A sometimes important distinction is that the second approach places objects in the namespace but the first does not. So if it is important that the function sees mytable as an object from the package, it is system data and the second approach should be used.
Putting the data in the internal data file made my function hello2() see it
> testingtop::hello2()
[1] "test_data has 32 rows"
[1] "Hello !"
To supplement Benoit's answer. I had essentially this problem, but when using my package data as a default for a function parameter. In that case there's a third solution in the ?data help file: "In the unusual case that a package uses a lazy-loaded dataset as a default argument to a function, that needs to be specified by ::, e.g., survival::survexp.us."
This third approach solved it for me. (But I found it thanks to Benoit's link.)

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"

Export error on overwriting primitives with S3 in R package

I'm trying to create a S3 method in my package called dimnames. This is a primitive in R, but there should be an S3 in my package with the same name.
I've got the following file dimnames.r
#' S3 overwriting primitive
#'
#' #param x object
#' #export
dimnames = function(x) {
UseMethod("dimnames")
}
#' title
#'
#' #export
dimnames.data.frame = function(x) {
dimnames.default(x)
}
#' title
#'
#' #export
dimnames.list = function(x) {
lapply(x, dimnames)
}
#' title
#'
#' #export
dimnames.default = function(x) {
message("in S3 method")
base::dimnames(x)
}
I then create a package from it (in R=3.3.2):
> package.skeleton("rpkg", code_files="dimnames.r")
> setwd("rpkg")
> devtools::document() # version 1.12.0
And then check the package
R CMD build rpkg
R CMD check rpkg_1.0.tar.gz
I get the following output (among other messages):
Warning: declared S3 method 'dimnames.default' not found
Warning: declared S3 method 'dimnames.list' not found
Loading the package and checking its contents, dimnames.data.frame is exported while dimnames.default and dimnames.list are not. This does not make sense to me. As far as I understand, I declared the exports correctly. Also, the NAMESPACE file looks good to me:
S3method(dimnames,data.frame)
S3method(dimnames,default)
S3method(dimnames,list)
export(dimnames)
Why does this not work, and how to fix it?
(Bonus points for: why do I need #' title in the S3 implementations when they should not be needed with roxygen=5.0.1?)
S3 methods are only exported if it is desired that the user be able to access them directly. If they are always to be invoked via the generic then there is no need to export them.
The problem with R CMD check is likely due to defining your own generic for dimnames. Normally one just defines methods and leverages off the primitive generic already in R. Remove the dimnames generic from dimnames.r.
There should be no problem in adding methods for new classes but you may have problems trying to override the functionality of dimnames for existing classes that R's dimnames handles itself.

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

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()

Resources