13.5 Object styles

So far I’ve focussed on vector style classes like Date and factor. These have the key property that length(x) represents the number of observations in the vector. There are three variants that do not have this property:

  • Record style objects use a list of equal-length vectors to represent individual components of the object. The best example of this is POSIXlt, which underneath the hood is a list of 11 date-time components like year, month, and day. Record style classes override length() and subsetting methods to conceal this implementation detail.

    x <- as.POSIXlt(ISOdatetime(2020, 1, 1, 0, 0, 1:3))
    #> [1] "2020-01-01 00:00:01 UTC" "2020-01-01 00:00:02 UTC"
    #> [3] "2020-01-01 00:00:03 UTC"
    #> [1] 3
    #> [1] 11
    x[[1]] # the first date time
    #> [1] "2020-01-01 00:00:01 UTC"
    unclass(x)[[1]] # the first component, the number of seconds
    #> [1] 1 2 3
  • Data frames are similar to record style objects in that both use lists of equal length vectors. However, data frames are conceptually two dimensional, and the individual components are readily exposed to the user. The number of observations is the number of rows, not the length:

    x <- data.frame(x = 1:100, y = 1:100)
    #> [1] 2
    #> [1] 100
  • Scalar objects typically use a list to represent a single thing. For example, an lm object is a list of length 12 but it represents one model.

    mod <- lm(mpg ~ wt, data = mtcars)
    #> [1] 12

    Scalar objects can also be built on top of functions, calls, and environments50. This is less generally useful, but you can see applications in stats::ecdf(), R6 (Chapter 14), and rlang::quo() (Chapter 19).

Unfortunately, describing the appropriate use of each of these object styles is beyond the scope of this book. However, you can learn more from the documentation of the vctrs package (https://vctrs.r-lib.org); the package also provides constructors and helpers that make implementation of the different styles easier.

13.5.1 Exercises

  1. Categorise the objects returned by lm(), factor(), table(), as.Date(), as.POSIXct() ecdf(), ordered(), I() into the styles described above.

  2. What would a constructor function for lm objects, new_lm(), look like? Use ?lm and experimentation to figure out the required fields and their types.

  1. You can also build an object on top of a pairlist, but I have yet to find a good reason to do so.↩︎