I'm writing an S3 class in R that is just an integer with some attributes attached to it. If x1 and x2 are objects of this class (call it "myclass"), then I would like c(x1, x2) to return a vector of myclass objects with the original class definition and attributes intact. However, the documented behavior of c() is to remove attributes, so it would seem that I need to write my own c.myclass() method. My question is, how can I do this?
An example of the problem:
myclass <- function(x, n) structure(x, class="myclass", n=n)
x1 <- myclass(1, 5)
x2 <- myclass(2, 6)
c(x1, x2)
[1] 1 2
Here the result is just a vector of items of class numeric, and the original n attribute is gone.
Looking at the code for various packages, I sometimes see code like the following, in which we need to preserve the class attribute but nothing else:
c.myclass <- function(..., recursive = F) {
structure(c(unlist(lapply(list(...), unclass))), class="myclass")
}
Unfortunately I also cannot get this to work. The result of calling c.myclass(x1, x2) is a vector where the vector itself has class "myclass" but where each item in the vector has class numeric; I really want each item in the vector to have class "myclass". In practice I will also need to upgrade this method to preserve other attributes as well (like the attribute "n" in myclass).
An S3 class is the most prevalent and used class in R programming. It is easy to implement this class and most of the predefined classes are of this type. An S3 object is basically a list with its class attributes assigned some names. And the member variable of the object created is the components of the list.
S3 implements a style of object oriented programming called generic-function OO. This is different to most programming languages, like Java, C++ and C#, which implement message-passing OO. In message-passing style, messages (methods) are sent to objects and the object determines which function to call.
The simplest way to create a class in S3 is to create a list, and then assign that list to a new class. In traditional object-oriented languages, you would have well-defined methods and attributes, but R is a bit more fluid. The following code shows the creation of the list: p <- list(id=100, rate = 24.50, score = 250)
The constructor function should return an object that is a vector corresponding to r with class equal to growth.
Here is an example that does (I think) what you want via specific methods for c
and [
:
c.myclass <- function(..., recursive = FALSE) {
dots <- list(...)
ns <- sapply(dots, attr, which = "n")
classes <- rep("myclass", length(dots))
res <- structure(unlist(dots, recursive = FALSE), class = classes)
attr(res, "n") <- ns
res
}
`[.myclass` <- function (x, i) {
y <- unclass(x)[i]
ns <- attr(x, "n")[i]
class(y) <- "myclass"
attr(y, "n") <- ns
y
}
myclass <- function(x, n) structure(x, class = "myclass", n = n)
x1 <- myclass(1, 5)
x2 <- myclass(2, 6)
c(x1, x2)
c(x1, x2)[2]
But that is a fudge in that we have to manage handling the setting and subsetting of extra attributes to hold the n
. This is really just a numeric vector with an attribute for recording n
.
It might be more natural to work with the generic of all vectors, a list. Bit that is more involved and maybe the above is sufficient in your case?
This does work, but I assume you conclude that each vector element has class numeric because you're doing something like this:
foo <- c(x1, x2)
class(foo[1])
class(foo[2])
If that's the case and you want extracted elements to retain the myclass
attribute, you need to write a subset method "[.myclass"
to retain the attributes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With