Creating S3 methods in R - r

for an advanced programming in R class we've been asked to create a package. The package is to include a function, "lad", a dataset, "area", and 3 methods, "print.lad", "coef.lad", and "predict.lad".
I have my "lad" function saved, and when building/requiring my package the function runs just fine. However, I'm a bit confused on the usage of setMethod.
For example, I created a new .R script titled "print.lad" in my "R" folder within the package. This method is to write the coefficient vector from the output of "lad" to the console. We've been instructed to make the output of "lad" a list of type "lad" with "coefficients" being the first in the list.
We've never gone over methods in class, so I had to look around on the internet for help. After the information/parameters/etc section, my code for "print.lad" looks like this:
setMethod("print", "lad", function(object){
print(object$coefficients)
} )
I can see that this isn't correct, but I'm also puzzled as to how to apply this setMethod function. I don't wish for someone to give me a working chunk of code outright, but an example of the application of setMethod and a bit of insight would be greatly appreciated. Thank you!

Create an object:
object <- list(coefficients = c("a" = 3, "b" = 4))
Assign the object a class:
class(object) <- "lad"
S3 methods have the form function.class. To define a "print" method:
print.lad <- function(object) {
print("Here are the coefficients:")
print(object$coefficients)
}
S3 methods are then automatically dispatched based on the object's class
print(object)
# [1] "Here are the coefficients:"
# a b
# 3 4
As an aside, I think I read somewhere that you should define print methods using cat instead of print because it's easier to control and nest, but I can't seem to find the source for that. For small cases, it shouldn't matter much.

Related

How to define an S3 generic with the same name as a primitive function?

I have a class myclass in an R package for which I would like to define a method as.raw, so of the same name as the primitive function as.raw(). If constructor, generic and method are defined as follows...
new_obj <- function(n) structure(n, class = "myclass") # constructor
as.raw <- function(obj) UseMethod("as.raw") # generic
as.raw.myclass <- function(obj) obj + 1 # method (dummy example here)
... then R CMD check leads to:
Warning: declared S3 method 'as.raw.myclass' not found
See section ‘Generic functions and methods’ in the ‘Writing R
Extensions’ manual.
If the generic is as_raw instead of as.raw, then there's no problem, so I assume this comes from the fact that the primitive function as.raw already exists. Is it possible to 'overload' as.raw by defining it as a generic (or would one necessarily need to use a different name?)?
Update: NAMESPACE contains
export("as.raw") # export the generic
S3method("as.raw", "myclass") # export the method
This seems somewhat related, but dimnames there is a generic and so there is a solution (just don't define your own generic), whereas above it is unclear (to me) what the solution is.
The problem here appears to be that as.raw is a primitive function (is.primitive(as.raw)). From the ?setGeneric help page, it says
A number of the basic R functions are specially implemented as primitive functions, to be evaluated directly in the underlying C code rather than by evaluating an R language definition. Most have implicit generics (see implicitGeneric), and become generic as soon as methods (including group methods) are defined on them.
And according to the ?InternalMethods help page, as.raw is one of these primitive generics. So in this case, you just need to export the S3method. And you want to make sure your function signature matches the signature of the existing primitive function.
So if I have the following R code
new_obj <- function(n) structure(n, class = "myclass")
as.raw.myclass <- function(x) x + 1
and a NAMESPACE file of
S3method(as.raw,myclass)
export(new_obj)
Then this passes the package checks for me (on R 4.0.2). And I can run the code with
as.raw(new_obj(4))
# [1] 5
# attr(,"class")
# [1] "myclass"
So in this particular case, you need to leave the as.raw <- function(obj) UseMethod("as.raw") part out.

How to overload S4 slot selector `#` to be a generic function

I am trying to turn the # operator in R into a generic function for the S3 system.
Based on the chapter in Writing R extensions: adding new generic I tried implementing the generic for # like so:
`#` <- function(object, name) UseMethod("#")
`#.default` <- function(object, name) base::`#`(object, name)
However this doesn't seem to work as it breaks the # for the S4 methods. I am using Matrix package as an example of S4 instance:
Matrix::Matrix(1:4, nrow=2, ncol=2)#Dim
Error in #.default(Matrix::Matrix(1:4, nrow = 2, ncol = 2), Dim) :
no slot of name "name" for this object of class "dgeMatrix"
How to implement a generic # so it correctly dispatches in the case of S4 classes?
EDIT
Also interested in opinions about why it might not be a good idea?
R's documentation is somewhat confusing as to whether # is already a generic or not: the help page for # says it is, but it isn't listed on the internalGenerics page.
The # operator has specific behaviour as well as (perhaps) being a generic. From the help page for #: "It is checked that object is an S4 object (see isS4), and it is an error to attempt to use # on any other object." That would appear to rule out writing methods for S3 classes, though the documentation is unclear if this check happens before method dispatch (if there is any) or after (whence it could be skipped if you supplied a specific method for some S3 class).
You can implement what you want by completely redefining what # is, along the line of the suggestion in comments:
`#.default` <- function(e1,e2) slot(e1,substitute(e2))
but there are two reasons not to do this:
1) As soon as someone loads your package, it supersedes the normal # function, so if people call it with other S4 objects, they are getting your version rather than the R base version.
2) This version is considerably less efficient than the internal one, and because of (1) you have just forced your users to use it (unless they use the cumbersome construction base::"#"(e1,e2)). Efficiency may not matter to your use case, but it may matter to your users' other code that uses S4.
Practically, a reasonable compromise might be to define your own binary operator %#%, and have the default method call #. That is,
`%#%` <- function(e1,e2) slot(e1,substitute(e2))
setGeneric("%#%")
This is called in practice as follows:
> setClass("testClass",slots=c(a="character")) -> testClass
> x <- testClass(a="cheese")
> x %#% a
[1] "cheese"

How to suggest hints to Rstudio for auto completion for my code?

Normally if a is a data.frame then one can autocomplete the column names by doing a$ tab. The chunked package has a nice feature where if you run
a <- chunked::read_csv_chunkwise("some.csv")
then when you type a[ then tab then it will show a list of variable via autocompletion even though a is not a data.frame.
I was trying to replicate this for my own code but I couldn't find any relevant resources after googling for "rstudio autocompletion" and various other searches.
I note that class(a) returns
[1] "chunkwise" "tbl"
I had a look at all the functions that belong to the S3 class "chunked" and I note that it has a method called tbl_vars, so I thought maybe that's what Rstudio uses to do the autocomplete.
So to test it out I tried
write.csv(data.frame(a = 1, b = 2), file = "test.csv",row.names = F)
tbl_vars.test_auto_complete <- function(fs) {
names(fread(fs$path))
}
test_auto_complete <- list(path = "test.csv")
class(test_auto_complete) <- "test_auto_complete"
tbl_vars(test_auto_complete)
[1] "a" "b"
But then when I type test_auto_complete tab the auto-complete doesn't show the variables that I want.
How can we give hints to Rstudio to make auto-completion work?
For objects that inherit from the tbl class, RStudio does indeed call tbl_vars() to populate completions. (This is an RStudio-specific autocompletion system feature.)
In your example, the object you're creating does not inherit from tbl, so this autocompletion pathway doesn't kick in.
However, this form of 'ad-hoc' S3 dispatch (where you define S3 methods directly as code like this) is not detected by RStudio, so you won't be able to verify this with test code like this. You'll have to explicitly define and register the S3 method in an R package.
Alternatively, you can try explicitly registering the S3 method with something like:
registerS3method("tbl_vars", "test_auto_complete", tbl_vars.test_auto_complete)
for inline testing.

How to call a function that contains a comma in R?

When using S3 or S4 classes in R, it's common to set a class as generic my_generic and then use dots for each subtype my_generic.my_type. Lately, I've been seeing this pattern, but using commas instead of periods my_generic,my_type. The problem is that I can't use the help operator ? or enter the function name in the console because the comma is treated as an error. Is there a workaround? I've tried using backticks, but it doesn't work.
An example of this is the draw method in the ComplexHeatmap package:
methods(draw)
[1] draw.colorkey draw.details draw,HeatmapAnnotation-method
[4] draw,HeatmapList-method draw,Heatmap-method draw.key
draw,SingleAnnotation-method
Doing ?draw.colorkey works, but ?draw,HeatmapAnnotation-method doesn't.
First of all, it is terribly bad practice to call methods directly, especially with S4. The "functions with a comma" you're looking at, are actually S4 methods.
Help pages
To find the help page (if it exists), you can use quotation marks like this:
?"draw,Heatmap-method"
But success is not guaranteed. This heavily depends on whether the author of the package has separate help files for the methods, or used the correct aliases. In this particular case, you see that on the help page ?draw the author of the package added a couple of links to the specific methods.
Find all S4 methods
To get an idea about all the S4 methods alone , use showMethods instead of methods.
> library(ComplexHeatmap)
> showMethods("draw")
Function: draw (package ComplexHeatmap)
object="Heatmap"
object="HeatmapAnnotation"
object="HeatmapList"
object="SingleAnnotation"
See the internal code of a method
To get the actual method so you can see the internal code, use getMethod:
getMethod(draw, signature = "Heatmap")
Method Definition:
function (object, ...)
{...
}
.local(object, ...)
}
<environment: namespace:ComplexHeatmap>
Signatures:
object
target "Heatmap"
defined "Heatmap"
Use a specific S4 method (but don't really)
You can assign the result of that call and use that as a function:
mat = matrix(rnorm(80, 2), 8, 10)
mat = rbind(mat, matrix(rnorm(40, -2), 4, 10))
rownames(mat) = letters[1:12]
colnames(mat) = letters[1:10]
ht = Heatmap(mat)
myMethod <- getMethod(draw, signature = "Heatmap")
myMethod(ht)
But you shouldn't try to call a method directly. The result of that last call is the exact same as
draw(ht)
So you better use the generic function and let the dispatching do its work.

Use one Class of an Object with Multiple

This is a general question motivated by a specific event.
When an object holds multiple classes, each with different generic actions, how can I specify to use "this" class, rather than "that" class?
The example code here is bundled with geepack.
library(stargazer)
library(geepack)
data(dietox)
dietox$Cu <- as.factor(dietox$Cu)
mf <- formula(Weight~Cu*(Time+I(Time^2)+I(Time^3)))
gee0 <- glm(mf, data = dietox, family = poisson("identity")) # a wrong model
gee1 <- geeglm(mf, data=dietox, id=Pig, family=poisson("identity"),corstr="ar1")
class(gee0)
class(gee1)
summary(gee0)
summary(gee1)
stargazer(gee0, type = "text")
stargazer(gee1, type = "text")
I'd like to work with the "glm" class object, not the "geeglm" class object.
#Richard Scriven: I'd just like to pull the results out into a stargazer(...) report. Thanks for the clarifying question.
The class system that uses the class(foo) attribute is not strongly typed. The class vector is used by R to determine which methods to use when that object is passed to a generic like print. For example, if you were to call print(gee1), R would first search for a function called print.geeglm which, in this case, it would find in the package geepack, and R calls that function with the arguments supplied to print().
If R did not find a function called print.geeglm, it would then search for print.gee, then print.glm, then print.default.
So in short, gee1 does not contain 3 objects with different classes, it is a single object with a class vector that informs R where to look for generic methods.
To make things slightly more confusing R has multiple type systems and the class vetcor is used by the S3 type system. A google search for "R s3 class" will get you lots more info on R's class system.

Resources