15.6 S4 and S3

When writing S4 code, you’ll often need to interact with existing S3 classes and generics. This section describes how S4 classes, methods, and generics interact with existing code.

15.6.1 Classes

In slots and contains you can use S4 classes, S3 classes, or the implicit class (Section 13.7.1) of a base type. To use an S3 class, you must first register it with setOldClass(). You call this function once for each S3 class, giving it the class attribute. For example, the following definitions are already provided by base R:

setOldClass("data.frame")
setOldClass(c("ordered", "factor"))
setOldClass(c("glm", "lm"))

However, it’s generally better to be more specific and provide a full S4 definition with slots and a prototype:

setClass("factor",
  contains = "integer",
  slots = c(
    levels = "character"
  ),
  prototype = structure(
    integer(),
    levels = character()
  )
)
setOldClass("factor", S4Class = "factor")

Generally, these definitions should be provided by the creator of the S3 class. If you’re trying to build an S4 class on top of an S3 class provided by a package, you should request that the package maintainer add this call to their package, rather than adding it to your own code.

If an S4 object inherits from an S3 class or a base type, it will have a special virtual slot called .Data. This contains the underlying base type or S3 object:

RangedNumeric <- setClass(
  "RangedNumeric",
  contains = "numeric",
  slots = c(min = "numeric", max = "numeric"),
  prototype = structure(numeric(), min = NA_real_, max = NA_real_)
)
rn <- RangedNumeric(1:10, min = 1, max = 10)
rn@min
#> [1] 1
rn@.Data
#>  [1]  1  2  3  4  5  6  7  8  9 10

It is possible to define S3 methods for S4 generics, and S4 methods for S3 generics (provided you’ve called setOldClass()). However, it’s more complicated than it might appear at first glance, so make sure you thoroughly read ?Methods_for_S3.

15.6.2 Generics

As well as creating a new generic from scratch, it’s also possible to convert an existing S3 generic to an S4 generic:

setGeneric("mean")

In this case, the existing function becomes the default (ANY) method:

selectMethod("mean", "ANY")
#> Method Definition (Class "derivedDefaultMethod"):
#> 
#> function (x, ...) 
#> UseMethod("mean")
#> <bytecode: 0x555b9d724480>
#> <environment: namespace:base>
#> 
#> Signatures:
#>         x    
#> target  "ANY"
#> defined "ANY"

NB: setMethod() will automatically call setGeneric() if the first argument isn’t already a generic, enabling you to turn any existing function into an S4 generic. It is OK to convert an existing S3 generic to S4, but you should avoid converting regular functions to S4 generics in packages because that requires careful coordination if done by multiple packages.

15.6.3 Exercises

  1. What would a full setOldClass() definition look like for an ordered factor (i.e. add slots and prototype the definition above)?

  2. Define a length method for the Person class.