How to create new method for generics in ggplot2? - r

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?

Related

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?

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.

Creating an RPackage - UseMethod can't find function

I'm trying on my first attempt on creating an R package. I have some functions that follows below.
#' #export
overview <- function(x, ...) {
UseMethod("overview")
}
overview.query <- function(return.query, ...) {
Now when I use the devtools::load_all() (which loads all functions) everything works, and overview.query is executed when I pass an object of class query.
But rebuilding, and the UseMethod can't find the overview.query function anymore (all functions are thus not loaded), what have I done wrong?
Error message: no applicable method for 'overview' applied to an object of class "c('query', 'data.frame')"
I thought that only functions that are to be exposed to the user are to be #export'ed, and all other functions would still be visible internally to the other package functions.
When you create a generic function to apply to an S3 object, you need to export both the UseMethod statement and the function itself, as in:
#' #export
overview <- function(x, ...) {
UseMethod("overview")
}
#' #export
overview.query <- function(return.query, ...) {
which ought to eliminate the error as that method is now available to the user.
This is applicable to roxygen2 versions 3+ (currently on 5). See this answer for more info:
How to properly document S3 methods

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