Abstract types in R - r

I want to create class in R, let's say it is an S4 class for a person. E.g.
setClass("Person", slots = list(name = "character", mood = "myMoodType"))
Now I want to create myMoodType to be an abstract type that can only take the three values "Happy", "Sad" and "Unknown".
I know that I could do this using the validity for S4 classes and have the mood as a character type and check the validity by verifying that the character string provided is one of the three options I list. But I would like to know if I can define an abstract type, like in julia, e.g.
abstract myMoodType
type Happy <: myMoodType end
type Sad <: myMoodType end
type Unknown <: myMoodType end
What would be the correct way to approach this in R?

This might not be one of R's strongest and most smooth feature, but you could solve it in the following way. For more information see the documentation or the Advanced R chapter on S4.
First set up the Person class with the mood represented as a factor, and link it to a validation function that checks its levels.
check_person <- function(object) {
if(identical(levels(object#mood), c("Happy", "Sad", "Unknown"))){
return(TRUE)
} else {
return("Invalid mood.")
}
}
setClass("Person",
representation(name = "character", mood = "factor"),
prototype = list(name = NA_character_,
mood = factor(NA, c("Happy", "Sad", "Unknown"))),
validity = check_person)
Creating new instances with new is however a bit messy since we have to write out all the levels each time:
john <- new("Person", name="John", mood=factor("Happy", levels=c("Happy", "Sad", "Unknown")))
lucy <- new("Person", name="Lucy", mood=factor("Sad", levels=c("Happy", "Sad", "Unknown")))
Otherwise we'll get an error:
new("Person", name="Eve", mood="Unknown")
Error in validObject(.Object) :
invalid class “Person” object: invalid object for slot "mood" in class "Person":
got class "character", should be or extend class "factor"
To get around that you could make your own constructor:
new_person <- function(name, mood){
new("Person", name = name, mood = factor(mood, levels = c("Happy", "Sad", "Unknown")))
}
new_person("Eve", "Unknown")
An object of class "Person"
Slot "name":
[1] "Eve"
Slot "mood":
[1] Unknown
Levels: Happy Sad Unknown

Related

How to make an S4 class inherit correctly from another S4 class?

I am creating two S4 classes, where class Employee inherits from the other class Person.
The definition for both the classes is as follows:
setClass("Person", slots = list(name="character", age="numeric"))
setClass("Employee", slots = list(boss="Person"))
I am creating once instance each of these two classes,
alice <- new("Person", name="Alice", age = 40)
This works well, but when I try to create an instance of Employee using :
john <- new("Employee", name = "John", age = 20, boss= alice)
It gives the error as below :
Error in initialize(value, ...) :
invalid names for slots of class “Employee”: name, age
Can I not create the object in this fashion ?
Per nrussel's comment:
the argument contains of the function setClass deals with inheritance. You want the class Employee to inherit from the class Person (i.e. an employee is a special type of person). So
setClass("Person", slots = list(name="character", age="numeric"))
setClass("Employee", slots = list(boss="Person"), contains = "Person")
will do the trick.
> alice <- new("Person", name="Alice", age = 40)
> john <- new("Employee", name = "John", age = 20, boss= alice)
> john
An object of class "Employee"
Slot "boss":
An object of class "Person"
Slot "name":
[1] "Alice"
Slot "age":
[1] 40
Slot "name":
[1] "John"
Slot "age":
[1] 20

Define particular slot as separate class R

My aim is to define one of the slots of class Security as another class Quote.
First I define class Quote:
Quote <- setClass("Quote", slots = c(Last = "numeric", Settle = "numeric"))
Then I am trying to define class Security as following:
Security <- setClass("Security", slots = c(Name = "character", Price = "Quote"))
Finally I am trying to create constructor for class Security:
Security <- function(Name = character(), Last = numeric(), Settle = numeric())
new("Security", Name = Name, Price#Last = Last, Price#Settle = Settle)
Unfortunately, this code doesn't work...
Thanks in advance.
If offering the user a constructor named Security, make sure the default constructor is named differently
.Security <- setClass("Security", slots = c(Name = "character", Price = "Quote"))
In your own constructor, create the slot instance as an argument to default constructor; use ... to allow for class inheritance
Security <-
function(Name = character(), Last = numeric(), Settle = numeric(), ...)
{
.Security(Name=Name, Price=Quote(Last=Last, Settle=Settle), ...)
}
I'm still trying to learn S4, and I see that a recognized expert has already given an answer, so I'm mostly posting this as an example for critique:
.Quote <- setClass("Quote", slots = c(Last = "numeric", Settle = "numeric"))
.Security <- setClass("Security", slots = c(Name = "character", Price = "Quote"))
aNewSecurity <- .Security(Name = "newSec",
Price = .Quote(Last =20, Settle = 40) )
aNewSecurity
An object of class "Security"
Slot "Name":
[1] "newSec"
Slot "Price":
An object of class "Quote"
Slot "Last":
[1] 20
Slot "Settle":
[1] 40
I'm not sufficiently knowledgeable to know if separating Quote items from Security items is needed in this domain.

How to create an S4 array which has slots containing arrays

How do I create an S4 class which has arrays as slots? Below, I have an example class. I'd like to be able to construct in such a way that I get two "person" elements, each of which has appropriate array members.
The code below gives me the following error: "Error in validObject(.Object) :
invalid class “person” object: invalid object for slot "children" in class "person": got class "character", should be or extend class "array"
setClass("person", representation(name="character", age="numeric", children = "array"))
setMethod(
f = "[",
signature="person",
definition=function(x,i,j,...,drop=TRUE){
initialize(x, name=x#name[i], age = x#age[i], children = x#children[i])
}
)
setMethod(f = "length", signature = "person", definition = function(x){
length(x#name)
})
setMethod(f = "dim", signature = "person", definition = function(x){
length(x#name)
})
kids1 = as.array(c("Bob", "Joe", "Mary"))
person = new("person", name="John", age=40, children = kids1)
person#children[2]
kids2 = as.array(c("Steve", "Beth", "Kim"))
people = new("person", name=c("John", "Fred"), age=c(40, 20), children = as.array(c(kids1, kids2), dim = 2))
people[1]#age
people[2]#children[1]
You probably don't want #children to be an array. With a dim attribute of length 1, it's essentially the same as a vector, and you lose the ability to distinguish different people's children. Consider making this slot a list instead.
setClass("person",
representation(name="character", age="numeric", children = "list"))
person = new("person", name="John", age=40, children = list(kids1))
person#children
people = new("person", name=c("John", "Fred"), age=c(40, 20),
children = list(kids1, kids2))
people[1]
Add drop=FALSE to your subset of the children slot; this is a consequence of standard R rules for array subsetting
setMethod(
f = "[",
signature="person",
definition=function(x,i,j,...,drop=TRUE){
initialize(x, name=x#name[i], age = x#age[i],
children = x#children[i,drop=FALSE])
}
)
Also I'm not sure that your as.array() is doing what you think? The dim argument is being ignored.
Hong's answer works. I did need to add a list wrapper in the "[" subset function. Once that's done, everything works fine.
kids1 = as.array(c("Bob", "Joe"))
kids2 = as.array(c("Steve", "Beth", "Kim"))
setClass("person", representation(name="character", age="numeric", children = "list"))
setMethod(
f = "[",
signature="person",
definition=function(x,i,j,...,drop=TRUE){
initialize(x, name=x#name[i], age = x#age[i], children = list(x#children[i]))
}
)
people = new("person", name=c("John", "Fred"), age=c(40, 20), children = list(kids1, kids2))
people[1]#name
people[1]#age
people[1]#children
people[2]#name
people[2]#age
people[2]#children

Why am I getting the message "node stack overflow" when the superclass is "VIRTUAL"?

I am getting the message
Error in parent.frame() : node stack overflow
Error during wrapup: node stack overflow
when I try to construct an object using the S4 command "as", but only when a superclass is declared "VIRTUAL".
The class hierarchy is as follows:
PivotBasic contains Pivot contains Model
The setClass commands for Pivot and Pivot Basic and the constructor for PivotBasic are below. Class Pivot does not have a constructor. The Model constructor is too big to insert here.
This is really not a big deal (I think) because everything works fine if the "VIRTUAL" keyword is removed from the representation argument of setClass. But I am curious about the reason for the problem. Would anyone have insights on it?
Thanks,
Fernando Saldanha
setClass(Class = "Pivot",
representation = representation(
pivotName = "character",
pivotNames = "character",
pivotData = "data.frame",
"VIRTUAL"
),
contains = "Model"
)
setClass(Class = "PivotBasic",
representation = representation(),
contains = "Pivot"
)
pivotBasic <- function(
portfolio,
assets,
controlVariableList,
pivotData = NULL, # pivotName is ignored if pivotData is not null
pivotName = "N_WEEKDAY_3_6",
firstPredictionDate = as.Date(integer(), origin = "1970-01-01"),
name = NULL,
tags = "Event"
) {
if (missing(portfolio)) stop("[PivotBasic: pivotBasic] - Missing portfolio argument")
if (missing(assets)) stop("[PivotBasic: pivotBasic] - Missing assets argument")
if (missing(controlVariableList)) stop("[PivotBasic: pivotBasic] - Missing controlVariableList argument")
object <- model(
portfolio,
assets,
controlVariableList,
firstPredictionDate,
name,
tags)
# The error message happens when this command is executed
mdl <- as(object, "PivotBasic")
# Other code
mdl
} # end pivotBasic
Is this a minimal example that illustrates your problem
.Model <- setClass(Class = "Model",
representation=representation(x="integer")
)
setClass(Class = "Pivot",
representation = representation("VIRTUAL"),
contains = "Model"
)
.PivotBasic <- setClass(Class = "PivotBasic",
contains = "Pivot"
)
This generates an error
> as(.Model(), "PivotBasic")
Error: evaluation nested too deeply: infinite recursion / options(expressions=)?
> R.version.string
[1] "R version 3.0.0 Patched (2013-04-15 r62590)"
but might generate an error like you see under an earlier version of R. This thread on the R-devel mailing list is relevant, where a solution is to define a setIs method such as
setIs("PivotBasic", "Model",
coerce = function(from) .PivotBasic(x = from#x),
replace = function(from, value) {
from#x = value#x
from
}
)
I think of setIs as part of the class definition. If there are many slots needing copying, then a further work-around might be, in the replace function,
nms <- intersect(slotNames(value), slotNames(from))
for (nm in nms)
slot(from, nm) <- slot(value, nm)
from
but the underlying issue is really in S4's implementation. A cost to removing the "VIRTUAL" specification is that it compromises your class design, and presumably the formalism of the S4 system is what motivated your choice in the first place; maybe that's not such a bad cost when faced with the alternatives.

S4 constructors and prototypes

Looking through Hadley Wickham's S4 wiki:
https://github.com/hadley/devtools/wiki/S4
setClass("Person", representation(name = "character", age = "numeric"),
prototype(name = NA_character_, age = NA_real_))
hadley <- new("Person", name = "Hadley")
How can we design a constructor for Person (like this)
Person<-function(name=NA,age=NA){
new("Person",name=name,age=age)
}
that doesn't do this:
> Person()
Error in validObject(.Object) :
invalid class "Person" object: 1: invalid object for slot "name" in class "Person": got class "logical", should be or extend class "character"
invalid class "Person" object: 2: invalid object for slot "age" in class "Person": got class "logical", should be or extend class "numeric"
It looks like the answer is right there in your example:
Person<-function(name=NA_character_,age=NA_real_){
new("Person",name=name,age=age)
}
yields
> Person()
An object of class "Person"
Slot "name":
[1] NA
Slot "age":
[1] NA
> Person("Moi")
An object of class "Person"
Slot "name":
[1] "Moi"
Slot "age":
[1] NA
> Person("Moi", 42)
An object of class "Person"
Slot "name":
[1] "Moi"
Slot "age":
[1] 42
However, that is fairly un-S4 and duplicates the default values already assigned in the class definition. Maybe you'd prefer to do
Person <- function(...) new("Person",...)
and sacrifice the ability to call without named arguments?
I'd prefer giving the end-user some hints about argument types than the suggestion to use ... by #themel. Also forgoing the prototype and using length(x#name) == 0 as an indication that the field is un-initialized, using a People class rather than Person, reflecting the vectorized structure of R, and using ... in the constructor so derived classes can also use the constructor.
setClass("People",
representation=representation(
firstNames="character",
ages="numeric"),
validity=function(object) {
if (length(object#firstNames) != length(object#ages))
"'firstNames' and 'ages' must have same length"
else TRUE
})
People = function(firstNames=character(), ages=numeric(), ...)
new("People", firstNames=firstNames, ages=ages, ...)
And
People(c("Me", "Myself", "I"), ages=c(NA_real_, 42, 12))

Resources