How to create an S4 array which has slots containing arrays - r

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

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

Abstract types in 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

R refClass Methods

I am using R refClass example below.
Person = setRefClass("Person",fields = list(name = "character", age = "numeric")
) ## Person = setRefClass("Person",
Person$methods = list(
increaseAge <- function(howMuch){
age = age + howMuch
}
)
When I store this program in a file called Person.R and source it, it does not show any errors. Now I instantiate a new object.
p = new("Person",name="sachin",age=40)
And I try to invoke the method increaseAge, using p$increaseAge(40), and it shows the following error
Error in envRefInferField(x, what, getClass(class(x)), selfEnv) :
"increaseAge" is not a valid field or method name for reference class "Person"
I cannot figure out why it says that the method increaseAge is not a valid method name when I have defined it.
To specify a method independent of class definition, invoke the methods() function on the generator. Also, use either <<- or .self$age = for the assignment.
Person$methods(increaseAge=function(howMuch) {
age <<- age + howMuch
## alterenatively, .self$age = age + howMuch or .self$age <- age + howMuch
})
Remember that R works best on vectors, so think of a Persons class (modeling columns) representing all the individuals in your study, rather than a collection of Person instances (modeling rows).
I get an error using your code. I would do something like this:
Person = setRefClass("Person",
fields = list(name = "character", age = "numeric"),
methods = list(
increaseAge = function(howMuch) age <<- age + howMuch
))
> p = new("Person",name="sachin",age=40)
> p$increaseAge(5)
> p$age
[1] 45

Create a S4 super class - with code example

Okay, it took me a while to create a snippet of code that replicates my problem. Here it is. Notice that if you run the command new("FirstSet", id = "Input", multiplier = 2)
you will get the correct answer. However, if you try to create a class that contains both you will get the following: Error in .local(.Object, ...) : argument "id" is missing, with no default. This is literally the best I can do to explain/show the problem.
What in the world am I doing wrong?
setClass("Details",
representation(
ID = "character",
Anumber = "numeric"))
Input <- new("Details",
ID = "Input",
Anumber = 2)
setClass("FirstSet",
representation(
Anothernumber = "numeric"))
setGeneric(
name = "FirstSet",
def = function(object){standardGeneric("FirstSet")}
)
setMethod("initialize",
signature(.Object = "FirstSet"),
function (.Object, id, multiplier)
{ x = id#Anumber
y = x * multiplier
.Object#Anothernumber = y
return(.Object)
}
)
setClass("Super", contains = c("Details", "FirstSet"))
Corrected Code now gives a new error. I followed the instruction in the post and solved my problem. I also created a generic and a method for "Super", see code below,. Now, I get a new error. Error in .local(.Object, ...) : trying to get slot "Anumber" from an object of a basic class ("character") with no slots. Man, this is exhausting, I thought I had it.
The goal for details is, there will be many files that are serialized and methods are called depending on characteristics of the data in the file. Is this even possible in R or am I trying to do something that R cannot do?
New Code
setClass("Details",
representation(
ID = "character",
Anumber = "numeric"))
setGeneric("Details",
def = function(object){standardGeneric("Details")})
setMethod("initialize",
signature(.Object = "Details"),
function(.Object, ID = character(), Anumber = numeric()){
.Object#ID = ID
.Object#Anumber = 2
return(.Object)
})
setClass("FirstSet",
representation(
Anothernumber = "numeric"))
setGeneric(
name = "FirstSet",
def = function(object){standardGeneric("FirstSet")}
)
setMethod("initialize",
signature(.Object = "FirstSet"),
function (.Object, id = character(), multiplier = numeric())
{ x = id#Anumber
y = x * multiplier
.Object#Anothernumber = y
return(.Object)
}
)
setClass("Super", contains = c("Details", "FirstSet"))
setGeneric("Super",
def = function(object){standardGeneric("Super")})
setMethod("initialize",
signature(.Object = "Super"),
function(.Object, id = character(), Anumber = numeric()){
Details <- new("Details", ID = id, Anumber = Anumber)
FirstSet <- new("FirstSet", Anothernumber = Anothernumber)
Super <- new("Super", Details, FirstSet)
return(.Object)
})
The basic rule is that new("FirstSet") (or any non-virtual class) needs to work. Yours doesn't (because the intiailize arguments don't have default values). See this answer for some more guidelines.

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.

Resources