dputting an S4 object
As it currently stands, you cannot dput
this object. The code of dput
contains the following loop:
if (isS4(x)) {
cat("new(\"", class(x), "\"\n", file = file, sep = "")
for (n in slotNames(x)) {
cat(" ,", n, "= ", file = file)
dput(slot(x, n), file = file, control = control)
}
cat(")\n", file = file)
invisible()
}
This handles S4 objects recursively, but it relies on the assumption an S3 object will not contain an S4 object, which in your example does not hold:
> isS4(slot(poly.d,'polygons'))
[1] FALSE
> isS4(slot(poly.d,'polygons')[[1]])
[1] TRUE
Edit: Here is a work-around the limitations of dput
. It works for the example you provided, but I don't think that it will work in general (e.g. it does not handle attributes).
dput2 <- function (x,
file = "",
control = c("keepNA", "keepInteger", "showAttributes")){
if (is.character(file))
if (nzchar(file)) {
file <- file(file, "wt")
on.exit(close(file))
}
else file <- stdout()
opts <- .deparseOpts(control)
if (isS4(x)) {
cat("new(\"", class(x), "\"\n", file = file, sep = "")
for (n in slotNames(x)) {
cat(" ,", n, "= ", file = file)
dput2(slot(x, n), file = file, control = control)
}
cat(")\n", file = file)
invisible()
} else if(length(grep('@',capture.output(str(x)))) > 0){
if(is.list(x)){
cat("list(\n", file = file, sep = "")
for (i in 1:length(x)) {
if(!is.null(names(x))){
n <- names(x)[i]
if(n != ''){
cat(" ,", n, "= ", file = file)
}
}
dput2(x[[i]], file = file, control = control)
}
cat(")\n", file = file)
invisible()
} else {
stop('S4 objects are only handled if they are contained within an S4 object or a list object')
}
}
else .Internal(dput(x, file, opts))
}
And here it is in action:
> dput2(poly.d,file=(tempFile <- tempfile()))
> poly.d2 <- dget(tempFile)
> all.equal(poly.d,poly.d2)
[1] TRUE
In R, how can one make a method of an S4 object that directly adjusts the values inside the slots of that object?
I asked this question on the R-list myself, and found a work-around to simulate a pass by reference, something in the style of :
eval(
eval(
substitute(
expression(object@slot <<- value)
,env=parent.frame(1) )
)
)
Far from the cleanest code around I'd say...
A suggestion coming from the R-help list, uses an environment to deal with these cases.
EDIT : tweaked code inserted.
setClass("MyClass", representation(.cache='environment',masterlist="list"))
setMethod("initialize", "MyClass",
function(.Object, .cache=new.env()) {
.Object@masterlist <- list()
callNextMethod(.Object, .cache=.cache)
})
sv <- function(object,name,value) {} #store value
setMethod("sv",signature=c("MyClass","character","vector"),
function(object, name, value) {
object@.cache$masterlist[[name]] <- value
})
rv <- function(object,name) {} #retrieve value
setMethod("rv",signature=c("MyClass","character"),
function(object, name) {
return(object@.cache$masterlist[[name]])
})
Store S4 object in matrix
You may create a matrix with S4 objects as elements by putting the items first into a list.
slots <- list(
new(Class="Course"),
new(Class="Course"),
new(Class="Course"),
new(Class="Course"))
Then just set the dim
attribute accordingly to obtain a matrix:
dim(slots) <- c(2,2)
Example:
slots # don't bother
## [,1] [,2]
## [1,] ? ?
## [2,] ? ?
slots[[1,1]] # element access
## An object of class "Course"
## Slot "tutor":
## character(0)
Make parallel clusters within a S4 class
Brandon asked about putting different types of objects ('apple', 'orange') into a single slot; you're asking about using an S3 class in an S4 object. The notation c("SOCKcluster", "cluster")
is S3's way of saying that SOCKcluster
contains cluster
as a parent class. This is a duplicate of Example of Using an S3 Class in a S4 Object, not of S4 Classes: Multiple types per slot, and with a twist -- you have a (short) S3 class hierarchy, not just a single S3 class. The solution is in the same spirit, though, with
setOldClass(c("SOCKcluster", "cluster"))
A = setClass("A", representation(cluster="SOCKcluster"))
and then
> library(parallel)
> a = A(cluster=makePSOCKcluster(2))
> a
An object of class "A"
Slot "cluster":
socket cluster with 2 nodes on host 'localhost'
Trying to put an MPI cluster (requires snow
and Rmpi
) into your class fails
> a = A(cluster=makeCluster(2, "MPI"))
2 slaves are spawned successfully. 0 failed.
Error in validObject(.Object) :
invalid class "A" object: 1: invalid object for slot "cluster" in class "A": got class "spawnedMPIcluster", should be or extend class "SOCKcluster"
invalid class "A" object: 2: invalid object for slot "cluster" in class "A": got class "MPIcluster", should be or extend class "SOCKcluster"
invalid class "A" object: 3: invalid object for slot "cluster" in class "A": got class "cluster", should be or extend class "SOCKcluster"
Creating a class that supports cluster
would require reading of ?setOldClass
.
Is there a way/method to assign a R6 object to an S4 object slot?
There is a way to have an R6 object inside an S4 class, but involves tricking the S4 system by defining an S4
class called 'R6'. This will prevent the methods
package complaining that class R6 doesn't exist. Fortunately, it doesn't actually check whether the 'R6' object in the prototype is of the S4 type, allowing you to stick an actual R6 object in there.
library(R6)
R6cls <- R6::R6Class("R6obj",
public = list(val = 1, foo = function() "foo!")
)
setClass('R6')
setClass("S4wR6slot", slots = list(a = 'R6', b = "character"),
prototype = list(a = R6cls$new(), b = 'Hello'))
S4wR6slot <- function() new("S4wR6slot")
myS4 <- S4wR6slot()
class(myS4)
#> [1] "S4wR6slot"
#> attr(,"package")
#> [1] ".GlobalEnv"
isS4(myS4)
#> [1] TRUE
myS4@a
#> <R6obj>
#> Public:
#> clone: function (deep = FALSE)
#> foo: function ()
#> val: 1
Efficient way to define a class with multiple, optionally-empty slots in S4 of R?
In my opinion...
Approach 2
It sort of defeats the purpose to adopt a formal class system, and then to create a class that contains ill-defined slots ('A' or NULL). At a minimum I would try to make DataClass1 have a 'NULL'-like default. As a simple example, the default here is a zero-length numeric vector.
setClass("DataClass1", representation=representation(x="numeric"))
DataClass1 <- function(x=numeric(), ...) {
new("DataClass1", x=x, ...)
}
Then
setClass("MasterClass1", representation=representation(dataClass1="DataClass1"))
MasterClass1 <- function(dataClass1=DataClass1(), ...) {
new("MasterClass1", dataClass1=dataClass1, ...)
}
One benefit of this is that methods don't have to test whether the instance in the slot is NULL or 'DataClass1'
setMethod(length, "DataClass1", function(x) length(x@x))
setMethod(length, "MasterClass1", function(x) length(x@dataClass1))
> length(MasterClass1())
[1] 0
> length(MasterClass1(DataClass1(1:5)))
[1] 5
In response to your comment about warning users when they access 'empty' slots, and remembering that users usually want functions to do something rather than tell them they're doing something wrong, I'd probably return the empty object DataClass1()
which accurately reflects the state of the object. Maybe a show
method would provide an overview that reinforced the status of the slot -- DataClass1: none. This seems particularly appropriate if MasterClass1 represents a way of coordinating several different analyses, of which the user may do only some.
A limitation of this approach (or your Approach 2) is that you don't get method dispatch -- you can't write methods that are appropriate only for an instance with DataClass1
instances that have non-zero length, and are forced to do some sort of manual dispatch (e.g., with if
or switch
). This might seem like a limitation for the developer, but it also applies to the user -- the user doesn't get a sense of which operations are uniquely appropriate to instances of MasterClass1 that have non-zero length DataClass1 instances.
Approach 1
When you say that the names of the classes in the hierarchy are going to be confusing to your user, it seems like this is maybe pointing to a more fundamental issue -- you're trying too hard to make a comprehensive representation of data types; a user will never be able to keep track of ClassWithMatrixDataFrameAndTree because it doesn't represent the way they view the data. This is maybe an opportunity to scale back your ambitions to really tackle only the most prominent parts of the area you're investigating. Or perhaps an opportunity to re-think how the user might think of and interact with the data they've collected, and to use the separation of interface (what the user sees) from implementation (how you've chosen to represent the data in classes) provided by class systems to more effectively encapsulate what the user is likely to do.
Putting the naming and number of classes aside, when you say "difficult to extend for additional data types in the future" it makes me wonder if perhaps some of the nuances of S4 classes are tripping you up? The short solution is to avoid writing your own initialize
methods, and rely on the constructors to do the tricky work, along the lines of
setClass("A", representation(x="numeric"))
setClass("B", representation(y="numeric"), contains="A")
A <- function(x = numeric(), ...) new("A", x=x, ...)
B <- function(a = A(), y = numeric(), ...) new("B", a, y=y, ...)
and then
> B(A(1:5), 10)
An object of class "B"
Slot "y":
[1] 10
Slot "x":
[1] 1 2 3 4 5
Related Topics
How to Give Numbers to Each Group of a Dataframe with Dplyr::Group_By
What Does The "More Columns Than Column Names" Error Mean
Split Violin Plot with Ggplot2 with Quantiles
Split Data.Frame Row into Multiple Rows Based on Commas
How to Make Hyperlinks in The Pop-Up of a Tm_Bubbles Item
Loop with a Defined Ggplot Function Over Multiple Dataframes
How to Flatten The Data of Different Data Types by Using Sparklyr Package
Extract First N Digits from a String
Ggplot: Recommended Colour Palettes Also Distinguishable for B&W Printing
Grouped Bar Chart on R Using Ggplot2
Axis-Labeling in R Histogram and Density Plots; Multiple Overlays of Density Plots
Xaringan Slide Separator Not Separating Slides
Embed Instagram/Youtube into Shiny R App
Include Non-Cran Package in Cran Package
How to Extract Bold Text from a PDF Using R