S4 constructors and prototypes - r

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))

Related

R S4 object datatype hash

Is there a datatype for hash? I tried to genereate a class with datatype character for hash values but I got error:
Error in validObject(.Object) : invalid class "Picture" object: 1:
invalid object for slot "mD5sum" in class "Picture": got class "hash",
should be or extend class "character" invalid class "Picture" object
2: invalid object for slot "sHA1sum" in class "Picture": got class
"hash", should be or extend class "character" Calls: <.....
Generate class code:
setClass("Picture", slots=list(id="numeric", mD5sum="character", sHA1sum="character"))
Adding data to class (this gives error):
md5sum <- as.character(openssl::md5(file(full_file_path)))
sha1sum <- as.character(openssl::sha1(file(full_file_path)))
pic_obj <- new("Picture", id=1, mD5sum=md5sum, sHA1sum=sha1sum)
Full code chunk:
```{r}
setClass("Picture", slots=list(id="numeric", mD5sum="character", sHA1sum="character"))
full_file_path <- "testphoto.JPG"
md5sum <- as.character(openssl::md5(file(full_file_path)))
sha1sum <- as.character(openssl::sha1(file(full_file_path)))
pic_obj <- new("Picture", id=1, mD5sum=md5sum, sHA1sum=sha1sum)
```
No, hash is not an atomic type (the closest idea R has to a "data type"). Hash values (such as from openssl::md5) are generally output as character (though in principle they could be integer if the output was small enough).
If you want to know whether a class exists or not, you can use isClass:
> isClass("hash")
[1] FALSE
> isClass("character")
[1] TRUE

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

Bug or Feature in R's version 3.3 class constructing

Trying to build a new class on the base of an 'ordered' class, using R.version 3.3.1 tried also on new R-devel
R Under development (unstable) (2016-10-26 r71594) -- "Unsuffered Consequences"
I get an error:
> setClass('newFactor', representation = c(tempValue = 'character'), contains = c('ordered'))
Error in validObject(.Object) :
invalid class “classRepresentation” object: invalid object for slot "slots" in class "classRepresentation": got class "character", should be or extend class "list"
>
The same thing works on the stable Version 3.2.5
EDIT
OK thanks #hrbrmstr I understood that R 3.3 does not support using S3 objects inside S4 objects but I am still puzzled. This:
numWithId <- setClass("numWithId", slots = c(id = "character"),contains = "numeric")
should work but this :
numWithId <- setClass("numWithId", slots = c(id = "character"),contains = "factor")
should not, except it does!
> numWithId <- setClass("numWithId", slots = c(id = "character"),contains = 'factor')
> new('numWithId')
An object of class "numWithId"
integer(0)
Slot "id":
character(0)
Slot "levels":
character(0)
Slot ".S3Class":
[1] "factor"
On the other hand this does not work:
numWithId <- setClass("numWithId", slots = c(id = "character"),contains = 'ordered')
Error in makePrototypeFromClassDef(properties, ClassDef, immediate, where) :
in constructing the prototype for class “numWithId”: prototype has class “S4”, but the data part specifies class “integer”
When I add list to the contains = it works {I have a feeling that this is not the right way of doing this}
> setClass("numWithId", slots = c(id = "character"),contains = c('list', 'ordered'))
> new("numWithId")
An object of class "numWithId"
list()
Slot "id":
character(0)
Slot ".S3Class":
[1] "ordered" "factor"
Slot "levels":
character(0)
>
What is the right way of inheriting an S3 object in to an S4 object?

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

Using fields from object in for-loop | twitteR

I am kind of a newbie to R. I am trying to build a function to retrieve and output all the dimensions. I am using the twitteR package which has an object 'user'. I use the getUser() which outputs a 'user' class type of object. As a part of the twitteR packages documentation, this object is not subsettable but does have fields such as names, screenNames, description, etc., e.g.:
> g <- getUser("CNN")
> g$name
[1] "CNN"
> g$screenName
[1] "CNN"
> g$description
[1] "Bringing you breaking news and the most talked about stories. Join the conversation and let’s connect!"
> g$statusesCount
[1] 35605
> g$followersCount
[1] 10542191
This is my function that I am trying to create and am struggling with:
userInfo <- function(user) {
userDims<- c("name", "screenName", "id", "lastStatus", "description", "statusesCount", "followersCount", "favoritesCount", "friendsCount")
for(i in seq_along(userDims)){
userObj <- getUser(user)
userObj$userDims[i]
}
}
My question is: How do I concatenate userObj$ with each entry in userDim without getting this error:
Error in envRefInferField(x, what, getClass(class(x)), selfEnv) :
‘userDims’ is not a valid field or method name for reference class “user”**

Resources