How to export functions as S3 methods in my R package? - r

I am writing a package for my use. I created objects of class marco, say.
Then I wrote some methods, like print.marco, plot.marco, etc, which I would like to be applied with print(obj) where class(obj) = "marco". In the NAMESPACE file (created with roxygen2::document()), these functions are simply exported as such and not as S3methodand are not recognized as such by sloop::is_s3_method.
I searched the internet and I can't find an answer or clear example. Following In Hadley Wickham's R packages in my R script I simply document the functions adding #' #export print.marco, etc.
A minimal example
#' Prints a marco object
#' #param marco_obj A marco object.
#' #export print.marco
print.marco(marco_obj){
print(marco_obj$this_is_printable)
}
From the above mentioned book I read (bold mine)
S3 generics are just functions, so the same rules for functions apply. S3 methods always accompany the generic, so as long as you can access the generic (either implicitly or explicitly), the methods will also be available. In other words, you don’t need to do anything special for S3 methods. As long as you’ve imported the generic, all the methods will also be available.
I guess all I need to understand is how to import generics?
Can someone help?
EDIT
The problem was that I used #export print.marco. This overrides the creation of S3 methods. Putting simply #export works fine. Thanks to Roland for his comments below.

So, the answer was that i was using the wrong #export directive.
I used #export print.marco. This overrides the creation of S3 methods. Putting simply #export works fine. Thanks to Roland for his comments above.

Related

Proper way of setting a class with methods::setClass() inside R package

I'm writing a package that contains a reading function that needs to read date columns in a specific format. I'm using data.table, and a SO question suggests using the methods::setClass() and methods::setAs() functions to deal with this case.
So far so good, it works fine. But when I check the package to submit it to CRAN I get the following NOTE:
NOTE: Namespaces in Imports field not imported from: 'methods'
Apparently this is because I'm creating a build-time dependency on methods, not a run-time dependency. Hadley here suggests just adding a #importFrom methods setClass setAs line, which doesn't seem very elegant, but does the trick.
Since this Google Groups discussion I linked is fairly old by now, and this is the first time I have to use these functions in a package I'm writing, I've been wondering if there is a different recommendation on how to deal with these methods::setClass() inside packages nowadays.
Check below for a bit more detail on how I'm currently using these functions in my code:
#' Set class and method to read dates as formatted in GTFS to a 'Date' object
#'
#' This is a build-time dependency on methods, as opposed to a run-time
#' dependency, thus requiring the importFrom tag to avoid a NOTE when checking
#' the package on CRAN.
#'
#' #keywords internal
#' #importFrom methods setClass setAs
methods::setClass("gtfs_date")
methods::setAs(
"character",
"gtfs_date",
function(from) as.Date(from, format = "%Y%m%d")
)

How to export S3method both as method and normal function using roxygen2

There are similar, older questions out there, but since roxygen2 version 3.0.0 things have changed a bit (as I understand from other Q&A here on SO).
I have written an alternative function format.Date, which I want to export, both as method and as function.
Using the #export tag means roxygen2 recognises it as an S3-method for print, and registers it accordingly. And when I load my package, and print a date object, my method gets called. So far, so good.
But, when I then call format.Date, I still get the normal base-method. This also happens when I use debugonce(format.Date), the 'debug-mark' gets set on base::format.Date, so if my method gets called, nothing happens. Or if I want to inspect the source-code: very hard for a user to understand that what he sees with View(format.Date) is NOT what is executed.
And if a user looks into my package what functions I have provided, format.Date is not there.
So I want format.Date to be exported as both an S3-method, and as a normal function called format.Date. In order to do so, I expect my NAMESPACE file to contain both following lines:
S3method(format,Date)
export(format.Date)
Is this possible in roxygen2? I get the impression you could do this in earlier versions (as you could supply both #S3method/#method and #export), but I can't get it to work now.
Background-info: roxygen2 version 6.1.1 with R 3.5.1, run under Rstudio 1.1.453/MacOS 10.13.6
The ways I found are
#exportS3Method and an explicit #export line. Since roxygen2 changes often, this might change in the future:
#' #exportS3Method fortify
#' #export fortify.Date
Manually spelling out the NAMESPACE content (add no other #export or #method directives)
#' #rawNamespace S3method(fortify,Date)
#' export(fortify.Date)
Both will result in the NAMESPACE file containing the following lines, with the first one resulting in roxygen2 ordering things for you.
S3method(fortify,Date)
export(fortify.Date)

How to use S3 methods from another package which uses export rather than S3method in its namespace without using Depends or library()

I'm working on an R package at present and trying to follow the best practice guidelines provided by Hadley Wickham at http://r-pkgs.had.co.nz. As part of this, I'm aiming to have all of the package dependencies within the Imports section of the DESCRIPTION file rather than the Depends since I agree with the philosophy of not unnecessarily altering the global environment (something that many CRAN and Bioconductor packages don't seem to follow).
I want to use functions within the Bioconductor package rhdf5 within one of my package functions, in particular h5write(). The issue I've now run into is that it doesn't have its S3 methods declared as such in its NAMESPACE. They are declared using (e.g.)
export(h5write.default)
export(h5writeDataset.matrix)
rather than
S3method(h5write, default)
S3method(h5writeDataset, matrix)
The generic h5write is defined as:
h5write <- function(obj, file, name, ...) {
res <- UseMethod("h5write")
invisible(res)
}
In practice, this means that calls to rhdf5::h5write fail because there is no appropriate h5write method registered.
As far as I can see, there are three solutions to this:
Use Depends rather than Imports in the DESCRIPTION file.
Use library("rhdf5") or require("rhdf5") in the code for the relevant function.
Amend the NAMESPACE file for rhdf5 to use S3methods() rather than export().
All of these have disadvantages. Option 1 means that the package is loaded and attached to the global environment even if the relevant function in my package is never called. Option 2 means use of library in a package, which while again attaches the package to the global environment, and is also deprecated per Hadley Wickham's guidelines. Option 3 would mean relying on the other package author to update their package on Bioconductor and also means that the S3 methods are no longer exported which could in turn break other packages which rely on calling them explicitly.
Have I missed another alternative? I've looked elsewhere on StackOverflow and found the following somewhat relevant questions Importing S3 method from another package and
How to export S3 method so it is available in namespace? but nothing that directly addresses my issue. Of note, the key difference from the first of these two is that the generic and the method are both in the same package, but the issue is the use of export rather than S3method.
Sample code to reproduce the error (without needing to create a package):
loadNamespace("rhdf5")
rdhf5::h5write(1:4, "test.h5", "test")
Error in UseMethod("h5write") :
no applicable method for 'h5write' applied to an object of class
"c('integer', 'numeric')
Alternatively, there is a skeleton package at https://github.com/NikNakk/s3issuedemo which provides a single function demonstrateIssue() which reproduces the error message. It can be installed using devtools::install_github("NikNakk/s3issuedemo").
The key here is to import the specific methods in addition to the generic you want to use. Here is how you can get it to work for the default method.
Note: this assumes that the test.h5 file already exists.
#' #importFrom rhdf5 h5write.default
#' #importFrom rhdf5 h5write
#' #export
myFun <- function(){
h5write(1:4, "test.h5", "test")
}
I also have put up my own small package demonstrating this here.

Rd file name conflict when extending a S4 method of some other package

Actual question
How do I avoid Rd file name conflicts when
a S4 generic and its method(s) are not necessarily all defined in the same package (package containing (some of) the custom method(s) depends on the package containing the generic) and
using roxygenize() from package roxygen2 to generate the actual Rd files?
I'm not sure if this is a roxygen2 problem or a common problem when the generic and its method(s) are scattered across packages (which IMHO in general definitely is a realistic use-case scenario if you follow a modular programming style).
What's the recommended way to handle these situations?
Illustration
In package pkga
Suppose in package pkga you defined a generic method foo and that you've provided the respective roxygen code that roxygenize() picks up to generate the Rd file:
#' Test function
#'
#' Test function.
#'
#' #param ... Further arguments.
#' #author Janko Thyson \email{janko.thyson##rappster.de}
#' #example inst/examples/foo.R
#' #docType methods
#' #rdname foo-methods
#' #export
setGeneric(
name="foo",
signature=c("x"),
def=function(
x,
...
) {
standardGeneric("xFoo")
}
)
When roxygenizing() your package, a file called foo-methods.Rd is created in the man subdirectory that serves as the reference Rd file for all methods that might be created for this generic method. So far so good. If all of the methods for this generic are also part of your package, everything's good. For example, this roxygen code would make sure that documentation is added to foo-methods.Rd for the ANY-method of foo:
#' #param x \code{ANY}.
#' #return \code{TRUE}.
#' #rdname foo-methods
#' #aliases foo,ANY-method
#' #export
setMethod(
f="foo",
signature=signature(x="ANY"),
definition=cmpfun(function(
x,
...
) {
return(TRUE)
}, options=list(suppressAll=TRUE))
)
However, if package pkga provides the generic for foo and you decide in some other package (say pkgb) to add a foo-method for x being of class character, then R CMD check will tell you that there is a name clash with respect to Rd file names and/or aliases (as there already exists a Rd file foo-methods.Rd in pkga):
In package pkgb
#' #param x \code{character}.
#' #return \code{character}.
#' #rdname foo-methods
#' #aliases foo,character-method
#' #export
setMethod(
f="foo",
signature=signature(x="character"),
definition=cmpfun(function(
x,
...
) {
return(x)
}, options=list(suppressAll=TRUE))
)
To be more precise, this is the error that's thrown/written to file 00install.out
Error : Q:/pkgb/man/foo-methods.Rd: Sections \title, and \name must exist and be unique in Rd files
ERROR: installing Rd objects failed for package 'pkgb'
Due dilligence
I tried to change the values for #rdname and #aliases to foo_pkgb* (instead of foo*), but \title and \name still are set to foo when roxygenizing and thus the error remains. Any ideas besides manually editing the Rd files generated by roxygenize()?
EDIT 2012-12-01
In light of starting the bounty, the actual question might get a slightly broader flavor:
How can we implement some sort of an "inter-package" check with respect to Rd files and/or how can we consolidate S4 method help files scattered across packages into one single Rd file in order to present a single source of reference to the end-user?
The basic question is indeed "roxygenize"-only.
That's why I never had seen the problem.
While there are good reasons for the roxygenizing approach of package development,
I still see a very good reason not to go there:
Plea for much less extreme roxygenation
The resulting help pages tend to look extremely boring, not only the auto generated *.Rd files but also the rendered result.
E.g.
examples are often minimal, do not contain comments, are often not well formatted (using space, / new lines /..)
Mathematical issues are rarely explained via \eqn{} or \deqn{}
\describe{ .. } and similar higher level formatting is rarely used
Why is that? Because
1) reading and editing roxygen comments is so much more
"cumbersome" or at least visually unrewarding
than reading and editing *.Rd files in ESS or Rstudio or (other IDE that has *.Rd support built in)
2) If you are used that documentation
is the thing that's automatically generated at the end of your package building/checking
you typically tend to not considerung well written R documentation as something important
(but rather your R code, to which all the docs is just a comment :-)
The result of all that: People prefer writing documentation about their functions in vignettes or even blogs, github gists, youtube videos, or ... where it is very nice at the time of authoring, but is
pretty much detached from the code and bound to get outdated and withering (and hence, via Google search misleading your useRs)
--> The original motivation of roxygen of having code and documentation in the same place is entirely defeated.
I like roxygen and use it extensively at the time I create a new function...
and I keep and maintain it as long as my function is not in a package, or is not exported.
Once I decide to export it,
I run (the ESS equivalent of) roxygenize() once
and from then on take the small extra burden of maintaining a *.Rd file that is well formatted, contains its own comments (for me as author), has many nice examples, has its own revision control (git / svn / ...) history, etc.
I managed to generate NAMESPACE and *.Rd files for S4 methods for generics defined in another package than mine.
It took me the following steps:
Create NAMESPACE by hand as a workaround to a known roxygen2 bug.
Writing a NAMESPACE by hand is not so difficult at all!
Switch off NAMESPACE generation by roxygen2 in RStudio:
Build > more > Configure build tools > configure roxygen > do not use roxygen2 to generate NAMESPACE.
import the package containing the generic and export the S4 methods using exportMethods.
Write separate roxygen2 documentation for each of the S4 methods. Do not combine roxygen2 documentation (as I generally do for different methods of the same generic).
Add explicit roxygen tags #title and #description to the roxygen documentation of the S4 methods. Write #description explicitly, even if its value is identical as #title.
That makes it work for me.

How to properly document S4 methods using roxygen2

I've seen some discussions in SO and other places regarding how this should be or will be done in future versions of Roxygen2. However, I am stuck. How should I go about documenting a S4 generic, as well as its methods, using Roxygen2? A working example for a brand new generic/methods, as well as an example for extending base S4 generic would be incredibly useful. I do not want to have to make separate (mostly) redundant documentation for each S4 method of the same generic.
Due dilligence:
I have tracked down a useful example for the "extract" method. However, it appears to be outdated and incomplete for my question. It uses #slot tag in the class documentation, which is not (no longer?) supported. It only shows an extension of a core S4 method "[", rather than a complete Roxygen example including the documentation of the S4 generic.
How to properly document S4 "[" and “[<-“ methods using roxygen?
If I fully document a new S4 generic with title, description #param #return #name #aliases
#docType #rdname, and then document the S4 method with just the corresponding #name #aliases
#docType #rdname, I get the following R CMD check warning:
* checking for missing documentation entries ... WARNING
Undocumented S4 methods:
<< long list of apparently undocumented methods. E.g. generic 'plot' and siglist 'myClass1,ANY' >>
All user-level objects in a package (including S4 classes and methods)
should have documentation entries.
It looked at first as though none of my S4 methods documented in this fashion with roxygen2 had actually worked. However, so far, I've noticed that my extensions of the core method "show" do not have an associated error, even though they were documented in exactly the same way as the rest. Here is an example of the complete roxygen documentation I included above one of the show methods, that did NOT generate the missing-documentation error:
#' #name show
#' #aliases show,myClass2-method
#' #docType methods
#' #rdname show-methods
Thus, I'm at a loss. As you can see, I've included the convention for aliases for S4 methods described in the S4 documentation section of the R package manual, namely that methods should have an alias with the following name (without space):
generic,signature_list-method.
Somehow, this is not completely being understood by R CMD check.
Finally, after building the documentation using:
library("devtools")
library("roxygen2")
document("mypkgname")
check_doc("mypkgname")
and building the package, I get functioning documentation. Any titles from a specific method's documentation winds up in the 'Description' field, rather awkwardly. So roxygen2 obviously did something with each method's documentation and is on the right track. However, it is not enough to avoid a large and troubling warning from
> R CMD check mypkgname
I've looked at the Rd files, but I know even less about them to quickly see what's the problem is, and I anyway want to know the roxygen2 solution so that I will not have to manipulate the Rd files directly after each documentation revision.
So this is a multipart question:
What is the current recommended approach for both S4 generic and S4 method documentation with roxygen2?
Is there a good example available somewhere that shows the complete details?
What might be the cause, and solution, to the warning that documentation for most S4 methods is missing (even though the methods with "missing" documentation actually have had their doc parsed by roxygen2 and the resulting Rd files are at least good enough to work in the package after R CMD build mypkgname) ?
Official Support, explained in devtools doc
http://r-pkgs.had.co.nz/man.html#man-classes
(scroll down to the S4 subsection).
The current short example from that page is reproduced in the following (for convenience):
#' An S4 class to represent a bank account.
#'
#' #slot balance A length-one numeric vector
Account <- setClass("Account",
slots = list(balance = "numeric")
)
The above link very clearly explains S3, S4, and RC support via roxygen/devtools. The explanation there should be considered to supersede the older answer below, which does work for now, but is less clear and more complicated.
The old answer
Here is an example that should work for most S4 methods.
For documenting S4 generics, I find the following three lines necessary in most of my generic headers:
#' #export
#' #docType methods
#' #rdname helloworld-methods
Where helloworld-methods is replaced with the_name_of_your_generic-methods. This will be the name of the Rd file that holds the documentation for the generic and all its methods. This naming convention is not required, but common and useful. The #export tag is necessary now that a namespace is required for your package and you want this method to be available to users of your package, not just other methods/functions in your package.
For documenting methods, I find that only 2 lines are necessary, providing the #rdname and #aliases tag. The #rdname statement should match exactly the one for the generic. The #aliases tag must adhere to a naming convention described in the official S4 documentation section of Writing R Extensions.
#' #rdname helloworld-methods
#' #aliases helloworld,character,ANY-method
There should be no spaces after the commas in the #aliases name. I don't know the exact rules surrounding when to add ,ANY to the end of the signature list. The pattern seems to be that the number of elements in all #aliases signature lists needs to match the number of elements in the longest signature vector among the methods. In the example below, one of the methods is defined with 2-element signature, and so R CMD check wasn't satisfied with the documentation unless ,ANY was added to the aliases tag of the other methods, even if their signature definition only has one element.
A complete example follows. I built this and it works with no documentation-level warnings from R CMD check testpkg, using a bug-fixed fork of a very recent devel version of roxygen2 (which I have made available). For quick installation of this fork on your system, use library("devtools"); install_github("roxygen", "joey711"). The most recent version of roxygen2 won't work this moment because of a quotation error (described in a separate answer), but I expect this will be incorporated very soon and my fork won't be necessary. For looking at this example in context of the rest of a package, and seeing the resulting documentation (Rd) files, I have made it available as a trivial test package on github called testpkg.
##############################################################
#' The title, in this case: Helloworld-ify the argument.
#'
#' Some additional details about this S4 generic and its methods.
#' The extra blank line between this section and the title is
#' critical for roxygen2 to differentiate the title from the
#' description section.
#'
#' #param x Description of \code{x}. The main argument in this
#' example. Most often has such and such properties.
#'
#' #param y Description of \code{y}. An argument that is rarely
#' used by \code{"helloworld"} methods.
#'
#' #param ... Additional argument list that might not ever
#' be used.
#'
#' #return A helloworld-ified argument. Oh, you'll see.
#'
#' #seealso \code{\link{print}} and \code{\link{cat}}
#'
#' #export
#' #docType methods
#' #rdname helloworld-methods
#'
#' #examples
#' helloworld("thisismystring")
#' helloworld(char2helloworld("thisismystring"))
#' helloworld(matrix(0,3,3))
#' helloworld(list(0,0,0))
#' helloworld(integer(0))
setGeneric("helloworld", function(x, y, ...){
cat("Hello World!")
cat("\n")
standardGeneric("helloworld")
})
#' #rdname helloworld-methods
#' #aliases helloworld,ANY,ANY-method
setMethod("helloworld", "ANY", function(x, y, ...){
cat(class(x))
})
#' #rdname helloworld-methods
#' #aliases helloworld,character,ANY-method
setMethod("helloworld", "character", function(x){
show(x)
})
#' #rdname helloworld-methods
#' #aliases helloworld,character,character-method
setMethod("helloworld", c("character", "character"), function(x, y){
show(x)
})
This example assumes that you don't need separate method-specific documentation because your methods haven't veered wildly from the behavior one would expect based on the generic doc. There are means in roxygen2 to handle that alternative case using the #usage tag, but the details would be better handled by a separate SO question.
So most of your documentation effort goes into the roxygen header above the generic definition. This has some basis in the code, since the generic should include all specific arguments that appear in any of the subsequently defined methods . Note that arguments that are handled as part of the ... in the argument list are not included and shouldn't be documented specifically, but the ... itself should be documented above the generic as well (see the full example below).
For further details on the basics of documenting functions, there is a wiki in progress, the old roxygen vignette, as well as the roxygen2 development site on github. There is also a SO question on R documentation with Roxygen in general.
I have tracked down that the answer to part (3) -- the not-so-missing documentation of S4 methods -- is because of quotation marks being added erroneously around the \alias tags when the S4 naming convention is used; most likely a bug resulting from text-handling of an alias that contains a comma as part of its name. This is still true of the latest version of roxygen2 at the time of this posting. I just posted a more thorough description of the bug with a reproducible example on the roxygen2 github page:
https://github.com/klutometis/roxygen/issues/40
Briefly,
#' #aliases show,helloworld-method
becomes
\alias{"show,helloworld-method"}
in the resulting Rd file. Removing the quotations removes the R CMD check warning, and the documentation builds and is functional in both cases.
I think parts (1) and (2) of this question (best practices; good examples) still stand open.

Resources