Jeff Allen
Jeff Allen

Reputation: 17537

Use a Reference Class as Levels of a Factor

Does anyone have any experience using Reference Classes as the levels of a factor? This is one step in my goal of adding "foreign-key-like" support to a data.frame in one of my packages.

I'm ultimately looking to create a data.frame which can store, as integers, the codes associated with a factor, but the levels of this factor are actually Reference Class objects, rather than the character vectors factors typically use. I've been able to find some information on using S4 objects as levels of a factor, but the techniques don't seem to be working for Reference Classes.

For instance, a simple:

> myClass <- setRefClass("MyClass", fields=list(a="numeric", b="character"))
> myObj <- myClass$new(a=1, b="test")
> factor(myObj)
Error in unique.default(x) : unique() applies only to vectors
> as.character(myObj)
Error in as.vector(x, "character") : 
  cannot coerce type 'environment' to vector of type 'character'
> as.character.MyClass <- function(x){ x$b }
> as.character(myObj)
[1] "test"
> factor(myObj)
Error in unique.default(x) : unique() applies only to vectors
>
> unique.MyClass <- function (x, incomparables = FALSE, ...) { unique(as.character(x)) }
> factor(myObj)
Error in as.vector(x, mode) : invalid 'mode' argument
> traceback()
2: as.vector(exclude, typeof(x))
1: factor(myObj)

doesn't seem to get anything working. It looks like, at my best, I can get to the line in factor() which analyzes the exclude parameter:

exclude <- as.vector(exclude, typeof(x))

at which point everything falls apart, since I'm not allowed to create a vector of type "S4".

Any thoughts on how to solve this or -- even better -- alternative approaches to map an integer to an Reference Class object in a data.frame-friendly way would be much appreciated!


Edit: In response to @Aaron's question below:

The simple solution here would be to just store integers in the data.frame and then maintain a separate list/data.frame responsible for mapping those IDs to some other data (such as Reference Class objects). This would maintain all of the necessary data to accomplish what I need, but would be less elegant, to me, for a couple of reasons:

  1. I'm envisioning a solution that would allow me to print out some portion of the Reference Class object itself, rather than the ID. For instance, if I'm mapping employee IDs (integer) to an "Employee" class, I would like my data.frame to print the names of the employees, rather than their IDs.
  2. I'd like to be able to extract the objects directly from the data.frame. In the same way that as.character(myDataFrame$someColumn) would give me the labels of that column (assuming it's a factor) rather than the underlying integers actually being stored in the data.frame.

Again, I'm very open to alternative solutions to this problem if there's a better way to go about it!

Upvotes: 2

Views: 494

Answers (1)

Aaron - mostly inactive
Aaron - mostly inactive

Reputation: 37814

I think this could be done by making a new class, say, myFactor, with the necessary methods; format seems to be the one you need for printing in a data frame; you could easily add print or other methods of your choice. I'm most comfortable with S3 methods, so that's what I've done here.

Here I set up both your reference class and the format function for the myFactor class.

myClass <- setRefClass("MyClass", fields=list(a="numeric", b="character"))
format.myFactor <- function(obj, ...) {
  n <- sapply(levels(obj), function(x) x[["b"]])
  n[obj]
}

Here I make two new reference objects, and use them as levels of my new myFactor object.

myObj <- myClass$new(a=1, b="test")
myObj2 <- myClass$new(a=4, b="testagain")
foo <- structure(c(2,1,2), levels=c(myObj, myObj2), class=c("myFactor","numeric"))
d <- data.frame(x=foo)

It then prints nicely.

> d
          x
1 testagain
2      test
3 testagain

Upvotes: 1

Related Questions