To extend the tibble package for new types of columnar data, you need to understand how printing works. The presentation of a column in a tibble is powered by two S3 generics:

  • type_sum() determines what goes into the column header.
  • obj_sum() is used when rendering list columns.

If you have written an S3 or S4 class that can be used as a column, you can override these generics to make sure your data prints well in a tibble. To start, you must import the tibble package. Either add tibble to the Imports: section of your DESCRIPTION, or simply call:

devtools::use_package("tibble")

This vignette assumes a package that implements an S3 class "foo" and uses roxygen2 to create documentation and the NAMESPACE file:

#' @export
foo <- function(x) {
  stopifnot(is.numeric(x))
  structure(x, class = "foo")
}

type_sum()

This method should return a length-1 character vector that can be used in a column header. Strive for an evocative abbreviation that’s under 6 characters.

type_sum(1)
#> [1] "dbl"
type_sum(1:10)
#> [1] "int"
type_sum(Sys.time())
#> [1] "dttm"

The default implementation works reasonably well for any kind of object, but the generated output may be too wide and waste precious space when displaying the tibble:

type_sum(foo(1:10))
#> [1] "S3: foo"

To avoid this for provide a method for type_sum():

#' @export
type_sum.foo <- function(x, ...) {
  "foo"
}

type_sum(foo(1:10))
#> [1] "foo"

obj_sum()

This method is primarily used for displaying list columns. A list column is a powerful way to attach hierarchical or unstructured data to an observation in a data frame. Implementations of obj_sum() are expected to return a character vector as long as the input, with brief description of the contents of each input element.

Examples:

obj_sum(1)
#> [1] "dbl [1]"
obj_sum(1:10)
#> [1] "int [10]"
obj_sum(Sys.time())
#> [1] "dttm [1]"
obj_sum(list(1:5))
#> [1] "int [5]"
obj_sum(list("a", "b", "c"))
#> [1] "chr [1]" "chr [1]" "chr [1]"

The default implementation calls type_sum() and appends the size of the object in brackets. If your object is built on top of an atomic vector the default will be adequate. You, will, however, need to provide a method if your object is vectorised and built on top of a list.

An example of an object of this type in base R POSIXlt: it is a list with 9 components.

x <- as.POSIXlt(Sys.time() + c(0, 60, 3600)) 
str(unclass(x))
#> List of 11
#>  $ sec   : num [1:3] 50 50 50
#>  $ min   : int [1:3] 41 42 41
#>  $ hour  : int [1:3] 7 7 8
#>  $ mday  : int [1:3] 14 14 14
#>  $ mon   : int [1:3] 3 3 3
#>  $ year  : int [1:3] 117 117 117
#>  $ wday  : int [1:3] 5 5 5
#>  $ yday  : int [1:3] 103 103 103
#>  $ isdst : int [1:3] 1 1 1
#>  $ zone  : chr [1:3] "CDT" "CDT" "CDT"
#>  $ gmtoff: int [1:3] -18000 -18000 -18000
#>  - attr(*, "tzone")= chr [1:3] "" "CST" "CDT"

But it pretends to be a vector with 3 elements:

x
#> [1] "2017-04-14 07:41:50 CDT" "2017-04-14 07:42:50 CDT"
#> [3] "2017-04-14 08:41:50 CDT"
length(x)
#> [1] 3
str(x)
#>  POSIXlt[1:3], format: "2017-04-14 07:41:50" "2017-04-14 07:42:50" ...

So we need to define a method that returns a character vector the same length as x:

#' @export
obj_sum.POSIXlt <- function(x) {
  rep("POSIXlt", length(x))
}