Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to set value in S4 object r using a method (no input value needed)

Tags:

oop

r

s4

This is a two part Question. I want to set the value of a prototype s4 object based on a different slots value and alternatively I want to implement this as a method.

I have an object I am trying to create. It has some slots. I would like to set a slots value based on the values put in from another slot. This is a simplified version of what I want to do.

i.e.,

setClass("Person",
    representation(name = "character", age = "numeric", doubleAge = "numeric"),
    prototype(name = "Bob", age = 5, doubleAge = 10) )

now I want to create an object but have the doubleAge value set itself based on the age slot.

p1 <- new("Person", name = "Alice", age = 6)

I see

An object of class "Person"                                                                                                      
Slot "name":                                                                                                                     
[1] "Alice"                                                                                                                  

Slot "age":                                                                                                                    
[1] 6                                                                                                                        

Slot "doubleAge":                                                                                                              
[1] 10       

but I want to see doubleAge be 12. In the prototype, I do not know how to change doubleAge = 10 to something like doubleAge = 2*age

So as a solution I tried to make a setting function init which sets the value after creation. This is the part 2 question.

setGeneric("init", "Person", function(object) {
    standardGeneric("init")
}
setMethod("init","Person", function(object) {
    object@doubleAge <- object@age*2
    object
}

if I print object@doubleAge in the method it returns 12 but it seems that the scope ends because it is 10 when it returns

Currently what works is very similar but it is not correct.

setGeneric("init<-", "Person", function(object) {
    standardGeneric("init<-")
}
setMethod("init<-","Person", function(object) {
    object@doubleAge <- object@age*2
    object
}

but then I have to call like init(p1) <- NULL which just seems weird. I know this example seems trivial but it is just a barebones example of a more complicated real world problem.

like image 604
channon Avatar asked Feb 20 '19 21:02

channon


2 Answers

It seems overriding the initialize method worked for me. For example

setClass("Person",
         representation(name = "character", age = "numeric", doubleAge = "numeric"),
         prototype(name = "Bob", age = 5, doubleAge = 10) )

setMethod("initialize", "Person", function(.Object, ...) {
  .Object <- callNextMethod()
  .Object@doubleAge <- .Object@age*2
  .Object
})

(p1 <- new("Person", name = "Alice", age = 6))
# An object of class "Person"
# Slot "name":
# [1] "Alice"
# Slot "age":
# [1] 6
# Slot "doubleAge":
# [1] 12

The callNextMethod() runs the "default" initializer to set up all the values that we are not messing with. Then we just change the values we want and return the updated object.

like image 99
MrFlick Avatar answered Oct 13 '22 15:10

MrFlick


In addition to the above method for initialize, you may want to set a method for @<-. The reason for this is if you do something like

x <- new("person", age=5)

then you have a valid person with age=5 and doubleage=10. But what if you now do

x@age <- 6

? Now age is 6 but doubleage is still 10 so the object is not valid any more.

The R documentation claims that you can write a method for @<- that will fix this:

setMethod("@<-",signature(object="Person"),function(object,name,value){
  if(name=="age"){
    object@age <- x
    object@doubleAge <- x*2
  } else if(e2=="doubleAge"){
    object@doubleAge <- x
    object@age <- value/2
  } else slot(object,name) <- value
  object
})

however when you actually run the above, you get an error:

Error in setGeneric(f, where = where) : ‘@<-’ dispatches internally; methods can be defined, but the generic function is implicit, and cannot be changed.

This is a strange looking error, since we are not trying to redefine the generic. In fact we find, when we try

method.skeleton("@<-",signature(object="Person"))

that R reluctantly tells us

Error in genericForBasic(name) : methods may not be defined for primitive function ‘@<-’ in this version of R

So if you want to have the slots reliably be consistent, we will have to write our own getters and setters, along the lines of

setAge <- function(x,value){
  x@age <- value
  x@doubleAge <- value*2
  x
}
like image 41
JDL Avatar answered Oct 13 '22 17:10

JDL