Let's say I define an S4 class 'foo'
with two slots 'a'
and 'b'
, and define an object x
of class 'foo'
,
setClass(Class = 'foo', slots = c(
a = 'numeric',
b = 'character'
))
x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
format(object.size(x), units = 'auto') # "16.5 Kb"
Then I want to remove slot 'a'
from the definition of 'foo'
setClass(Class = 'foo', slots = c(
b = 'character'
))
slotNames(x) # slot 'a' automatically removed!! wow!!!
I see that R automatically take cares my object x
and have the slot 'a'
removed. Nice! But wait, the size of object x
is not reduced.
format(object.size(x), units = 'auto') # still "16.5 Kb"
format(object.size(new(Class = 'foo', x)), units = 'auto') # still "16.5 Kb"
Right.. Somehow 'a'
is still there but I just cannot do anything to it
head(x@a) # `'a'` is still there
rm(x@a) # error
x@a <- NULL # error
So question: how can I really remove slot 'a'
from x
and have its size reduced (which is my primary concern)?
My deepest gratitude to all answers!
The following solution is inspired by dww
trimS4slot <- function(x) {
nm0 <- names(attributes(x))
nm1 <- names(getClassDef(class(x))@slots) # ?methods::.slotNames
if (any(id <- is.na(match(nm0, table = c(nm1, 'class'))))) attributes(x)[nm0[id]] <- NULL # ?base::setdiff
return(x)
}
format(object.size(y1 <- trimS4slot(x)), units = 'auto') # "8.5 Kb"
The following solution is inspired by Robert Hijmans
setClass('foo1', contains = 'foo')
format(object.size(y2 <- as(x, 'foo1')), units = 'auto') # "8.5 Kb"
method::as
probably does some comprehensive checks, so it's quite slow though
library(microbenchmark)
microbenchmark(trimS4slot(x), as(x, 'foo1')) # ?methods::as 10 times slower
How to create S4 objects? S4 objects are created using the new () function. > # create an object using new () > # provide the class name and value for slots > s <- new ("student",name="John", age=21, GPA=3.5) > s An object of class "student" Slot "name": [1] "John" Slot "age": [1] 21 Slot "GPA": [1] 3.5
In this article, you’ll learn everything about S4 classes in R; how to define them, create them, access their slots, and use them efficiently in your program. Unlike S3 classes and objects which lacks formal definition, we look at S4 class which is stricter in the sense that it has a formal definition and a uniform way to create objects.
Suppose we want to create an S4 with defined name data and two numerical columns called by x and y then we can use setClass ("data",representation (x1="numeric",x2="numeric")). Now, if we want to extract the variables of this S4 object then we would need to use @ sign instead of $ sign as in a data frame.
As in the case of S3 class, methods for S4 class also belong to generic functions rather than the class itself. Working with S4 generics is pretty much similar to S3 generics.
Slots are stored as attributes. We have a couple of options for converting a slot to NULL
.
Option 1: You can use the check=FALSE
argument in slot<-
to assign a slot as NULL without triggering an error.
slot(x, 'a', check=FALSE) <- NULL
setClass(Class = 'foo', slots = c(b = 'character'))
format(object.size(x), units = 'auto')
# [1] "8.7 Kb"
However, the attribute is not completely removed (it still exists with a value of \001NULL\001
). This happens because of a line in the C function R_do_slot_assign
, which has: if(isNull(value)) value = pseudo_NULL;
where pseudo_NULL is "an object that is ...used to represent slots that are NULL (which an attribute can not be)".
One should also take notice of the advice in ?slot
that "User's should not set check=FALSE
in normal use, since the resulting object can be invalid." It should not cause any issues in this instance, since the slot is being removed immediately after. Nonetheless it is good to be cautious about using the check=False flag unless you are sure you understand what you are doing.
Option 2: A more complete removal can be achieved by directly removing the attribute after removing the slot from the class definition:
setClass(Class = 'foo', slots = c(
b = 'character'
))
attr(x, 'a') <- NULL
format(object.size(x), units = 'auto')
# [1] "8.7 Kb"
But, is removing slots even a good idea?
Removing slots is something of a hack that could cause errors later, e.g. if a method is called that assumes the existence of the slot. You might get away with doing this for a specific use case on your own machine. But releasing this into the wild as production code would not be a good idea. In that case, the approach in @RobertHijmans' answer would be the way to go.
What @dww suggests is nifty, and answers your question. But isn't the point of a class that you are guaranteed that its members (the slots) will always be there? If you don't care about that you can use the anything goes S3
classes instead? With S4
, I would suggest a more formal approach like this:
setClass(Class = 'bar', slots = c(b = 'character'))
setClass(Class = 'foo', contains='bar', slots = c(a = 'numeric'))
x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
format(object.size(x), units = 'auto')
#[1] "16.5 Kb"
x <- as(x, "bar")
format(object.size(x), units = 'auto')
#[1] "8.5 Kb"
And if this is just about size, why not just do
x <- new('foo', a = rnorm(1e3L), b = rep('A', times = 1e3L))
x@b <- ""
format(object.size(x), units = 'auto')
#[1] "8.7 Kb"
To me this is clearly the best solution because of its simplicity.
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