I am trying to overload some functions in the context of S4-classes. Here is a sample code:
foo <- setClass(
Class = "foo",
slots = c("name" = "character", "value" = "numeric")
)
setMethod(f = "exp",
signature = c(x = "foo"),
definition = function(x) {
exp(x#value)
}
)
setMethod(f = "round",
signature = c(x = "foo", digits = "foo"),
definition = function(x, digits) {
round(x#value, digits#value)
}
)
setMethod(f = "log",
signature = c(x = "foo", base = "foo"),
definition = function(x, base) {
log(x#value, base#value)
}
)
While the exp and round functions work just fine, the log function does not with the following error:
Error in match.call(definition, call, expand.dots, envir) :
unused argument (base = c("foo", ""))
This is confusing as the args function states that the name of the second (optional) argument is indeed base.
Would you know what the issue is? Thanks.
From the help page for log,
"Note that this means that the S4 generic for log has a signature with only one argument, x, but that base can be passed to methods (but will not be used for method selection). On the other hand, if you only set a method for the Math group generic then base argument of log will be ignored for your class."
So, you can't use "base" in your signature. If you really need dispatch on the base argument, you will have to write a second generic:
myLog <- function(x,base) log(x,base)
setGeneric(myLog,signature=c("x","base"))
On a separate issue, you don't need to write separate definitions for every function like this, you can use group generics. In your example, you could write
setMethod("Math","foo",function(x)callGeneric(x#value))
or possibly
setMethod("Math","foo",function(x,...)callGeneric(x#value,...))
And this single line of code will make all of
[1] "abs" "sign" "sqrt" "ceiling" "floor" "trunc" "cummax" "cummin"
[9] "cumprod" "cumsum" "exp" "expm1" "log" "log10" "log2" "log1p"
[17] "cos" "cosh" "sin" "sinh" "tan" "tanh" "acos" "acosh"
[25] "asin" "asinh" "atan" "atanh" "cospi" "sinpi" "tanpi" "gamma"
[33] "lgamma" "digamma" "trigamma"
work as desired (by operating on the #value slot of foo). You might also want to do something similar for math2, which includes round.
A third way to achieve what you want is to have your "foo" class extend the numeric class:
setClass("foo",
slots = c("name" = "character"),
contains="numeric"
) -> foo
Then everything works as if the objects are actually numeric. If you need to get at the "data part" of an object x of class foo, then you can use foo#.Data.
Related
I've created a custom S4 class, and the idea is that it represents a vector that's always sorted, so I don't want sort() to actually do anything to it. So I defined a stub version of sort() for my class:
MyClass <- methods::setClass("MyClass", slots=list(x="numeric"))
setMethod("sort", signature(x="MyClass"), function(x, ...){}) # Do nothing
Then, I want to calculate a quantile of my class. R's quantile() function internally calls sort(). However, the sort() used inside quantile() is not aware of my S4 method, because it dispatches using UseMethod() (the S3 dispatcher) and not standardGeneric(), the S4 dispatcher. This is demonstrated below:
options(error=traceback)
instance = MyClass()
quantile(instance, 0.5)
This returns a call stack like this:
5: order(x, na.last = na.last, decreasing = decreasing)
4: sort.default(x, partial = unique(c(lo, hi)))
3: sort(x, partial = unique(c(lo, hi)))
2: quantile.default(instance, 0.5)
1: quantile(instance, 0.5)
Since sort.default is being called, it is evident that my custom sort implementation isn't being used.
Is there a simple way to get R to use my S4 method here? I realise I can also define sort.MyClass (the S3 way), but if I do this, what is the point of having an S4 method at all? It seems like S4 is incompatible with core R methods which renders it fairly useless.
Object instance is defined with a slot named x that is numeric. When you call quantile(instance, 0.5), R do not know that you want quantile to act on the slot instance#x.
Approach 1:
MyClass <- setClass("MyClass", slots = list(x = "numeric"))
setMethod(
"quantile",
signature(x = "MyClass"),
function(x, ...) {
callNextMethod(x#x, ...)
}
)
# test drive
instance <- MyClass(x = c(0, 5, 2, 1, 3))
quantile(instance, 0.5)
sort(instance) # error
mean(instance) # error
# see that quantile is now using S4 dispatch
quantile
standardGeneric for "quantile" defined from package "stats"
function (x, ...)
standardGeneric("quantile")
<environment: 0x000001fe1375fe08>
Methods may be defined for arguments: x
Use showMethods(quantile) for currently available ones.
# see method table for quantile
showMethods(quantile, includeDefs = TRUE)
Function: quantile (package stats)
x="ANY"
function (x, ...)
UseMethod("quantile")
x="MyClass"
function (x, ...)
{
callNextMethod(x#x, ...)
}
With this approach, you can see that quantile is automatically converted to using S4 dispatch.
The call quantile(instance, 0.5) is dispatch to quantile,MyClass-method
Inside quantile,MyClass-method, the code callNextMethod(x#x, ...) will dispatch to quantile,ANY-method with content of slot x as argument. This argument is numeric.
Inside quantile,ANY-method, the code will S3 dispatch the calling arguments to quantile.default.
However, This approach require you to specify a customized version of every functions to act on MyClass. Therefore sort(instance) and mean(instance) output error.
Approach 2: Make MyClass as a subclass of numeric. Then all functions that work on numeric will work on MyClass. Below, I add a customized initialize method to automatically sort its numeric argument. A sort,MyClass-method to do no sorting and only return MyClass as numeric for consistency.
MyClass <- setClass("MyClass", contains = "numeric")
setMethod("initialize",
signature(.Object = "MyClass"),
function (.Object, ...)
{
callNextMethod(.Object, sort(..1)) # ..1 is first element of ... see ?dots
}
)
setMethod(
"sort",
signature(x = "MyClass"),
function(x, decreasing = FALSE, ...) {
as(x, "numeric")
}
)
# test drive
instance <- MyClass(c(0, 5, 2, 1, 3))
quantile(instance, 0.5)
quantile(instance)
mean(instance)
sd(instance)
plot(instance)
Note:
setMethod("sort", signature(x="MyClass"), function(x, ...){}) # return NULL
setMethod("sort", signature(x="MyClass"), function(x, ...) x) # return x unchange
I have some trouble understanding the differences in method dispatching between S3 and S4 classes. As far as I understand, S3 classes use UseMethod and finds the correct method via the class attribute of the object passed. S4 classes use StandardGeneric and work with function signatures (I'm reading Advanced R). But the following code runs:
myfun <- function(x, y = NULL) {
UseMethod("myfun")
}
myfun.default <- function(x, y) {
print("default method")
}
myfun.integer <- function(x, y) {
print("S3 dispatch for integer")
}
setMethod("myfun",
signature = c(x = "matrix", y = "ANY"),
function(x, y) {
print("S4 dispatch for matrices")
}
)
setMethod("myfun",
signature = c(x = "character", y = "ANY"),
function(x, y) {
print("S4 dispatch for strings")
}
)
setMethod("myfun",
signature = c(x = "character", y = "character"),
function(x, y) {
print("S4 dispatch for string + string")
}
)
myfun(iris)
## [1] "default method"
myfun(1:10)
## [1] "S3 dispatch for integer"
myfun(matrix(0, nrow = 2, ncol = 2))
## [1] "S4 dispatch for matrices"
myfun("foo")
## [1] "S4 dispatch for strings"
myfun("foo", y = "bar")
## [1] "S4 dispatch for string + string"
What exactly is going on here? I created an S3 method called "myfun", for which the S3 method dispatch works as intended. So far, so good.
But this S3 method also correctly dispatches S4 methods, even though I didn't define a StandardGeneric for these S4 methods (or convert myfun to such). How come? Any background would be appreciated.
Thanks in advance!
I'm creating a S4 object of the money class in the following manner:
# Create class ------------------------------------------------------------
# Create S4 class object holding money and export to generator function
setClass(Class = "money",
slots = list(currency = "character",
value = "numeric")) -> money
For which I'm later defining the show method:
# Methods -----------------------------------------------------------------
# Create show method
setMethod("show",
"money",
function(object) {
cat(switch(object#currency,
GBP = intToUtf8(163)),
format(
x = round(object#value, 2),
trim = TRUE,
big.mark = ",",
big.interval = 3
),
sep = "")
})
Preview
So far it works as promised:
# Create test object
tst_fig <- new(Class = "money",
value = 1e6,
currency = "GBP")
# Show the object
tst_fig
# £1,000,000
Problem
I would like to enable basic arithmetic on that object:
>> tst_fig + 1e6
Error in tst_fig + 1000000 : non-numeric argument to binary operator
Desired results
> tst_fig + 1e6
# £2,000,000
Attempts
Naturally this is won't work:
>> setMethod("+",
... "money",
... function(object, x) {
... object#value + x
... })
Error in conformMethod(signature, mnames, fnames, f, fdef, definition) :
in method for ‘+’ with signature ‘e1="money"’: formal arguments (e1 = "money", e2 = "money") omitted in the method definition cannot be in the signature
Side notes
There is a similar excellent answer provided by #Roland on implementing money class in S3; in the context of this question I'm interested in creating S4 class that would behave in a similar manner without any specific reason other than curiosity. The key requirements is that isS4() on that object returns TRUE.
What I mean by similar manner:
It prints like nicely formatted money but permits all operations the one could do on a common numeric.
I came across how to do this in my own question here. I have generally used the setMethod('Arith') approach as it is more concise when you intend to implement several operations. If you search the documentation ?Arith you will see that it list the different operations as well as other S4 group generics.
As the error suggests you need to have e1 and e2 defined for Arith methods. In your specific case the following works.
Note - to get your desired output (i.e. a money class object) you will need to create a new money object.
setMethod("+",
c(e1="money", e2="numeric"),
function(e1, e2){
new(Class = "money", value = e1#value + e2, currency = e1#currency)
}
)
tst_fig + 1e6
[1] £2e+06
However, as I said, you probably want the more general, concise version which uses .Generic to interpret the Arith method you are using.
setMethod("Arith",
c(e1="money", e2="numeric"),
function(e1, e2)
{
op = .Generic[[1]]
switch(op,
`+` = return(new(Class = "money", value = e1#value + e2, currency = e1#currency))
)
}
)
I am attempting to write some classes in R. Here is the start of a neural network layer class. It is generating warnings and errors that I don't know how to correct.
# Slot definitions
setClass(
Class="neuralNetworkLayer",
representation=representation(
input = "vector",
linearOutput = "vector",
squashedOutput = "vector",
hasBias = "logical",
bias = "vector",
weights = "vector",
gains = "matrix",
squashFcn = "closure",
squashFcnDerivative = "closure"
)
)
# Constructors
NeuralNetworkLayer <- function(nInput,nOutput,hasBias=TRUE,squashFcn,squashFcnDerivative) {
nc = list(
input = c(rep(NA,nInput)),
linearOutput = c(rep(NA,nOutput)),
squashedOutput = c(rep(NA,nOutput)),
hasBias = hasBias,
bias = c(rep(NA,nOutput)),
weights = c(rep(NA,nOutput)),
gain = matrix(data=weights, nrow = nInput, ncol = nOutput),
squashFcn = squashFcn, # source of warning / error
squashFcnDerivative = squashFcnDerivative,
get = function(x) nc[[x]],
set = function(x, value) nc[[x]] <<- value,
props = list()
)
#Add a few more functions
nc$addProp = function(name, value) {
p <- nc$props
p[[name]] <- value
assign('props', p, envir=nc)
}
nc <- list2env(nc)
class(nc) <- "NeuralNetwork"
return(nc)
}
tanhDerivative <- function(x) {
d = 1 - tan(x)^2
return(d)
}
test <- NeuralNetworkLayer(nInput=4,nOutput=5,hasBias=TRUE,
squashFcn=tanh,squashFcnDerivative=tanhDerivative)
The messages generated are
Warning message:
undefined slot classes in definition of "neuralNetworkLayer": squashFcn(class "closure"),
squashFcnDerivative(class "closure")
Error in as.vector(x, mode) :
cannot coerce type 'closure' to vector of type 'any'
Both messages indicate that the base class closure can not be used for a slot. How to pass a function?
Taking the advice from the two answers, the following code can be generated. This addresses the original question of passing a function to a slot, and then using that function. For completeness, the revised neural network layer class is present.
setClass(
Class="neuralNetworkLayer",
representation=representation(
nInput = "numeric",
nOutput = "numeric",
squashFcn = "function",
derivSquashFcn = "function",
gains = "matrix",
hasBias = "logical",
bias = "matrix",
linOutput = "matrix",
squashOutput = "matrix"
)
)
getClass("neuralNetworkLayer")
getSlots("neuralNetworkLayer")
sf <- function(x){
f = tanh(x)
return(f)
}
dsf <- function(x) {
d = 1 - tan(x)^2
return(d)
}
# Create an object of class
hh = new("neuralNetworkLayer",squashFcn=sf,nInput=5,nOutput=5,hasBias=TRUE,
derivSquashFcn = dsf)
hh#squashFcn(3)
hh#derivSquashFcn(3)
The error/warning :
undefined slot classes in definition of "neuralNetworkLayer": squashFcn(class "closure")
means that slot is not defined beacuse the type "closure" is not defined.
You try To define a slot(attribute) as a generic function one idea is to use ANY ( the default for slot I think) type:
neuralNetworkLayer <-
setClass(
Class="neuralNetworkLayer",
representation=representation(
squashFcn = "ANY"
)
)
Then ,for example you instantiate your class like this :
# Constructors
hh = neuralNetworkLayer(squashFcn=function(x)print(x)) ## dummy function here
hh#squashFcn(10)
[1] 10
That's said , I think you should consider to define your functions slots as a real method(see setMethod) . Methods are to have typed (safer) objects Otheriwse there is no good reason the reason to use S4 system and easier to use S3 method.
I don't get why you are defining a neuralNetworkLayer S4 class while your constructor doesn't make any use of it. You just are creating a normal R list (no slots in the returning object of NeuralNetworkLayer). In your constructor you should call new somewhere.
Anyhow, your error doesn't have nothing to do with closure as you might think. You just didn't define the weights object that it happens to be also an R function. When you say:
weights = c(rep(NA,nOutput)),
gains = matrix(data=weights, nrow = nInput, ncol = nOutput),
you are creating an element of a list named weights, but you are not creating an object named weights. When you define the gains element, R just finds for weights its R function and tries to coerce it to put it into a matrix, giving the error. You can just define weights in your first line of NeuralNetworkLayer:
weights = c(rep(NA,nOutput))
and then, when you define nc, replace the first line I wrote above with:
weights = weights,
and your function won't give any error.
For the warning part of the definition of the S4 class, just use function instead of closure. However, as you have defined your constructor, you may well not define it at all.
Is it possible to call or set values for more then one slot?
A<-setClass(Class="A",slot=c(name="character",type="character"))
a<-A()
slot(object,c("name","type"),check=T)
Do I have to write own getSlot and setSlot methods? And how to that in R5?
AB <- setRefClass("AB", fields=c(name="character"),
methods=list(getName=AB.getName)
)
AB.getName<-function(object){
object$name
}
a<-AB(name="abc")
AB.getName(a)
This answer applies to reference classes.
Let's start with the simplest definition of AB, without any methods.
AB <- setRefClass(
"AB",
fields = list(
name = "character"
)
)
You can retrieve the value of the name field in the same way you would a list.
ab <- AB$new(name = "ABC")
ab$name
## [1] "ABC"
(ab$name <- "ABCD")
## [1] "ABCD"
It is possible to autogenerate accessor methods to get and set the name field.
AB$accessors("name")
ab$getName()
ab$setName("ABCDE")
This is really pointless though since it has the exactly same behaviour as before, but with more typing. What can be useful is to do input checking (or other custom behaviour) when you set a field. To do this, you can add a setName method that you write yourself.
AB$methods(
setName = function(x)
{
if(length(x) > 1)
{
warning("Only using the first string.")
x <- x[1]
}
name <<- x
}
)
ab$setName(letters)
## Warning message:
## In ab$setName(letters) : Only using the first string.
It is also possible (and usually more useful) to define this method when you assign the reference class template.
AB <- setRefClass(
"AB",
fields = list(
name = "character"
),
methods = list(
setName = function(x)
{
if(length(x) > 1)
{
warning("Only using the first string.")
x <- x[1]
}
name <<- x
}
)
)
Response to comment:
Yes that works, but:
getFieldNames is more maintainable if implemented as names(AB$fields()).
When defining fields in setRefClass, use a list. For example, list(name="character", var2="character").
When assigning an instance of a reference class, use new. For example, AB$new(name="abc",var2="abc")
In S4, the default initialize method allows one to write
A <- setClass(Class="A", slot=c(name="character",type="character"))
a <- A(name="abc", type="def")
initialize(a, name="cde", type="fgh")
Your own initialize methods (if any -- I think it's usually best to avoid them) have to be written to allow for this use. There is no default way to convert an S4 representation to a list.
You could incorporate these ideas into your own generics / methods with something like
setGeneric("values", function(x, ...) standardGeneric("values"))
setMethod("values", "A", function(x, ...) {
slts = slotNames(x)
lapply(setNames(slts, slts), slot, object=x)
})
setGeneric("values<-", function(x, ..., value) standardGeneric("values<-"))
setReplaceMethod("values", c(x="A", value="list"), function(x, ..., value) {
do.call("initialize", c(x, value))
})
with
> a <- A(name="abc", type="def")
> values(a) = list(name="cde", type="fgh")
> values(a)
$name
[1] "cde"
$type
[1] "fgh"