#> $levels
#> [1] "a" "b" "c"
#>
#> $class
#> [1] "factor"
class attributeAn S3 object has an attribute called “class” with at least one value.
print() is an S3 generic for displaying objects depending on their class attribute.print() is an S3 genericprint() behaves differently depending on the object’s classprint.factor() is a print() method for factorsMethod: A function customized for a particular class.
#> function (x, quote = FALSE, max.levels = NULL, width = getOption("width"),
#> ...)
#> {
#> ord <- is.ordered(x)
#> if (length(x) == 0L)
#> cat(if (ord)
#> "ordered"
#> else "factor", "()\n", sep = "")
#> else {
#> xx <- character(length(x))
#> xx[] <- as.character(x)
#> keepAttrs <- setdiff(names(attributes(x)), c("levels",
#> "class"))
#> attributes(xx)[keepAttrs] <- attributes(x)[keepAttrs]
#> print(xx, quote = quote, ...)
#> }
#> maxl <- max.levels %||% TRUE
#> if (maxl) {
#> n <- length(lev <- encodeString(levels(x), quote = ifelse(quote,
#> "\"", "")))
#> colsep <- if (ord)
#> " < "
#> else " "
#> T0 <- "Levels: "
#> if (is.logical(maxl))
#> maxl <- {
#> width <- width - (nchar(T0, "w") + 3L + 1L +
#> 3L)
#> lenl <- cumsum(nchar(lev, "w") + nchar(colsep,
#> "w"))
#> if (n <= 1L || lenl[n] <= width)
#> n
#> else max(1L, which.max(lenl > width) - 1L)
#> }
#> drop <- n > maxl
#> cat(if (drop)
#> paste(format(n), ""), T0, paste(if (drop)
#> c(lev[1L:max(1, maxl - 1)], "...", if (maxl > 1) lev[n])
#> else lev, collapse = colsep), "\n", sep = "")
#> }
#> if (!isTRUE(val <- .valid.factor(x)))
#> warning(val)
#> invisible(x)
#> }
#> <bytecode: 0x0000025824b39540>
#> <environment: namespace:base>
structure() or class()<-#> $class
#> [1] "my_class"
But currently, this is pretty useless…
A low-level constructor, new_myclass(), that efficiently creates new objects with the correct structure.
A validator, validate_myclass(), that performs more computationally expensive checks to ensure that the object has correct values.
A user-friendly helper, myclass(), that provides a convenient way for others to create objects of your class.
new_factor() creates the new S3 objectThe developer-facing function applies the correct structure for the object.
validate_factor() provides assurances on correctnessThough computationally expensive, the checks ensure correct values before creating the S3 object.
validate_factor <- function(x) {
values <- unclass(x)
levels <- attr(x, "levels")
if (!all(!is.na(values) & values > 0)) {
stop(
"All `x` values must be non-missing and greater than zero",
call. = FALSE
)
}
if (length(levels) < max(values)) {
stop(
"There must be at least as many `levels` as possible values in `x`",
call. = FALSE
)
}
x
}
validate_factor(new_factor(1:5, "a"))#> Error: There must be at least as many `levels` as possible values in `x`
#> Error: All `x` values must be non-missing and greater than zero
factor() provides a safe approach for users to create objectsThe user-facing validates the values before creating the S3 object.
tibble::new_tibble(), tibble::validate_tibble(), tibble::tibble()
dplyr::new_grouped_df(), dplyr::validate_grouped_df(), dplyr::grouped_df()
summary() is an S3 generic that behaves based on an object’s class#> [1] TRUE
#> function (object, ...)
#> UseMethod("summary")
#> <bytecode: 0x0000025822139b98>
#> <environment: namespace:base>
| generic | class | visible | source |
|---|---|---|---|
| summary | aov | TRUE | stats |
| summary | aovlist | FALSE | registered S3method |
| summary | aspell | FALSE | registered S3method |
| summary | check_packages_in_dir | FALSE | registered S3method |
| summary | connection | TRUE | base |
| summary | data.frame | TRUE | base |
| summary | Date | TRUE | base |
| summary | default | TRUE | base |
| summary | difftime | TRUE | base |
| summary | ecdf | FALSE | registered S3method |
| summary | factor | TRUE | base |
| summary | glm | TRUE | stats |
| summary | infl | FALSE | registered S3method |
| summary | lm | TRUE | stats |
| summary | loess | FALSE | registered S3method |
| summary | manova | TRUE | stats |
| summary | matrix | TRUE | base |
| summary | mlm | FALSE | registered S3method |
| summary | nls | FALSE | registered S3method |
| summary | packageStatus | FALSE | registered S3method |
| summary | POSIXct | TRUE | base |
| summary | POSIXlt | TRUE | base |
| summary | ppr | FALSE | registered S3method |
| summary | prcomp | FALSE | registered S3method |
| summary | princomp | FALSE | registered S3method |
| summary | proc_time | TRUE | base |
| summary | rlang:::list_of_conditions | FALSE | registered S3method |
| summary | rlang_error | FALSE | registered S3method |
| summary | rlang_message | FALSE | registered S3method |
| summary | rlang_trace | FALSE | registered S3method |
| summary | rlang_warning | FALSE | registered S3method |
| summary | srcfile | TRUE | base |
| summary | srcref | TRUE | base |
| summary | stepfun | TRUE | stats |
| summary | stl | FALSE | registered S3method |
| summary | table | TRUE | base |
| summary | tukeysmooth | FALSE | registered S3method |
| summary | vctrs_sclr | FALSE | registered S3method |
| summary | vctrs_vctr | FALSE | registered S3method |
| summary | warnings | TRUE | base |
summary() is an interface to different methodsPolymorphism: a single interface to different behaviors.
However, summary() does not have a consistent output class.
summarizer() S3 generic outputs a consistent object...summarizer() includes UseMethod for dispatchingCaution
You don’t pass any of the arguments of the generic to UseMethod(); it uses deep magic to pass to the method automatically. The precise process is complicated and frequently surprising, so you should avoid doing any computation in a generic. See ?UseMethod for details.
summarizer() outputs consistent summary stringsEncapsulation: Bundle data and methods into a single object or “unit”.
summarizer.numeric <- function(x, probs = c(0.25, 0.5, 0.75)) {
# Remove NAs to avoid warnings
x_no_na <- x[!is.na(x)]
qs <- quantile(x_no_na, probs = probs, names = FALSE)
names(qs) <- paste0("Q", seq_along(qs))
stats <- c(
Min = min(x_no_na),
qs,
Max = max(x_no_na)
)
paste0(
paste(names(stats), format(stats), sep = ": ", collapse = ", ")
)
}#> [1] "pear (3), apple (2), orange (1)"
#> [1] "c (3), a (2), b (2)"
#> [1] "Min: 1.0, Q1: 2.5, Q2: 4.0, Q3: 5.5, Max: 10.0"
#> [1] "Min: 1.0, Q1: 15.7, Q2: 205.0, Max: 250.0"
#> Sepal.Length
#> "Min: 4.3, Q1: 5.1, Q2: 5.8, Q3: 6.4, Max: 7.9"
#> Sepal.Width
#> "Min: 2.0, Q1: 2.8, Q2: 3.0, Q3: 3.3, Max: 4.4"
#> Petal.Length
#> "Min: 1.00, Q1: 1.60, Q2: 4.35, Q3: 5.10, Max: 6.90"
#> Petal.Width
#> "Min: 0.1, Q1: 0.3, Q2: 1.3, Q3: 1.8, Max: 2.5"
#> Species
#> "setosa (50), versicolor (50), virginica (50)"
#> inst
#> "Min: 1, Q1: 3, Q2: 11, Q3: 16, Max: 33"
#> time
#> "Min: 5.00, Q1: 166.75, Q2: 255.50, Q3: 396.50, Max: 1022.00"
#> status
#> "Min: 1, Q1: 1, Q2: 2, Q3: 2, Max: 2"
#> age
#> "Min: 39, Q1: 56, Q2: 63, Q3: 69, Max: 82"
#> sex
#> "Min: 1, Q1: 1, Q2: 1, Q3: 2, Max: 2"
#> ph.ecog
#> "Min: 0, Q1: 0, Q2: 1, Q3: 1, Max: 3"
#> ph.karno
#> "Min: 50, Q1: 75, Q2: 80, Q3: 90, Max: 100"
#> pat.karno
#> "Min: 30, Q1: 70, Q2: 80, Q3: 90, Max: 100"
#> meal.cal
#> "Min: 96, Q1: 635, Q2: 975, Q3: 1150, Max: 2600"
#> wt.loss
#> "Min: -24.00, Q1: 0.00, Q2: 7.00, Q3: 15.75, Max: 68.00"
summarizer() genericsInheritance: method dispatching through the “class” attribute.
length() for concise representation#> [1] "2020-01-01 00:00:01 CST" "2020-01-01 00:00:02 CST"
#> [3] "2020-01-01 00:00:03 CST"
#> [1] 3
#> [1] 11
#> [1] "2020-01-01 00:00:01 CST"
#> [1] 1 2 3
NextMethod()delegates dispatch behavior for classes without explicit genericssecret() masks each character of the input with x in the output[ generic for the secret class[ method is problematic in that it does not preserve the secret class.y[1] returns 15 as the first element instead of xx.Fix this with a [.secret method:
NextMethod() is an efficient way to subset a secret class[.secret is selected, but delegates to internal [.vctrs::vec_restore provides proper method chaining