How to call a function that contains a comma in R? - 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.

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"

type/origin of R's 'as' function

R's S3 OO system is centered around generic functions that call methods depending on the class of the object the generic function is being called on. The crux is that the generic function calls the appropriate method, as opposed to other OO programming languages in which the method is defined within the class.
For example, the mean function is a generic function.
isGeneric("mean")
methods(mean)
This will print
TRUE
[1] mean,ANY-method mean.Date mean.default mean.difftime
[5] mean.IDate* mean,Matrix-method mean.POSIXct mean.POSIXlt
[9] mean,sparseMatrix-method mean,sparseVector-method
see '?methods' for accessing help and source code
I was exploring R a bit and found the as function. I am confused by the fact that R says the function is not generic, but it still has methods.
isGeneric("as")
methods(as)
TRUE
[1] as.AAbin as.AAbin.character
[3] as.alignment as.allPerms
[5] as.array as.array.default
[7] as.binary as.bitsplits
[9] as.bitsplits.prop.part as.call
...
At the end there is a warning that says that as is not a generic.
Warning message:
In .S3methods(generic.function, class, parent.frame()) :
function 'as' appears not to be S3 generic; found functions that look like S3 methods
Could someone explain me what the as function is and how is connected to as.list, as.data.frame etc? R says that as.list is a generic (where I am tempted to get a bit mad at the inconsistencies within R, because I would expect as.list to be a method for a list object from the as generic function). Please help.
as is not an S3 generic, but notice that you got a TRUE. (I got a FALSE.) That means you have loaded a package that definesas as an S4-generic. S3-generics work via class dispatch that employs a *.default function and the UseMethod-function. The FALSE I get means there is no method defined for a generic as that would get looked up. One arguable reason for the lack of a generic as is that calling such a function with only one data object would not specify a "coercion destination". That means the destination needs to be built into the function name.
After declaring as to be Generic (note the capitalization which is a hint that this applies to S4 features:
setGeneric("as") # note that I didn't really even need to define any methods
get('as')
#--- output----
standardGeneric for "as" defined from package "methods"
function (object, Class, strict = TRUE, ext = possibleExtends(thisClass,
Class))
standardGeneric("as")
<environment: 0x7fb1ba501740>
Methods may be defined for arguments: object, Class, strict, ext
Use showMethods("as") for currently available ones.
If I reboot R (and don't load any libraries that call setGeneric for 'as') I get:
get('as')
#--- output ---
function (object, Class, strict = TRUE, ext = possibleExtends(thisClass,
Class))
{
if (.identC(Class, "double"))
Class <- "numeric"
thisClass <- .class1(object)
if (.identC(thisClass, Class) || .identC(Class, "ANY"))
return(object)
where <- .classEnv(thisClass, mustFind = FALSE)
coerceFun <- getGeneric("coerce", where = where)
coerceMethods <- .getMethodsTable(coerceFun, environment(coerceFun),
inherited = TRUE)
asMethod <- .quickCoerceSelect(thisClass, Class, coerceFun,
coerceMethods, where)
.... trimmed the rest of the code
But you ask "why", always a dangerous question when discussing language design, of course. I've flipped through the last chapter of Statistical Models in S which is the cited reference for most of the help pages that apply to S3 dispatch and find no discussion of either coercion or the as function. There is an implicit definition of "S3 generic" requiring the use of UseMethod but no mention of why as was left out of that strategy. I think of two possibilities: it is to prevent any sort of inheritance ambiguity in the application of the coercion, or it is an efficiency decision.
I should probably add that there is an S4 setAs-function and that you can find all the S4-coercion functions with showMethods("coerce").

Creating S3 methods in 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.

Include base type (e.g. list) as a class attribute?

I am writing an R package which revolves around analyzing some around data stored in a list. For example,
myData <- list(x = "x vector", y = "another vector",
z = "function(x,y)", meta = "info about this dataset")
The character values are just descriptions here since the values are not relevant. I have a few different kinds of data stored in a list format, but there is some overlap so I'm giving each kind a class attribute, like this
class(myData) <- "datatype1"
Now I've tested this, and seen that things like
length(myData) ; myData$x
still work, and I am under the impression that this is because the storage mode is still a list, and the operations I've tried are .Primitve or .Internal, but I don't know when/why this works.
My question is the following: Is there any circumstance in which I should worry about getting rid of 'list' as a class name? Should I instead use:
class(myData) <- c("datatype1", "list")
?
Basically I don't know if/when function dispatch fails if the class 'list' is not found but the storage mode is still a list. I've tried to read documentation and haven't seen this particular issue. If someone can point me to the relevant info, I'm happy to read the manual. Thanks!
In general some functions in some packages may be dispatched based on "list" class. For example as.data.frame or within in base R.
list1 <- list(a = 1)
class(list1) <- "nolist"
within(list1, a <- 4)
Error in UseMethod("within") :
no applicable method for 'within' applied to an object of class "nolist"
For a "list" dependent methods check
methods(class = "list")

Resources