In this lab, we will learn how to pass the dots.
Goal: by the end of this lab, you should be able to pass the dots appropriately.
The dots (...
) are a way of collecting an arbitrary set
of arguments to a function. There are two main purposes as to why you
might want to have the dots as an arguments to your function.
First, note that you can convert the dots into a named list
within your function using the list()
function.
<- function(...) {
catch_the_dots list(...)
}
catch_the_dots(whatever = "hi")
## $whatever
## [1] "hi"
You might want to pass the dots to another function from within your
function. This is typically useful when you want to leverage the
functionality of an existing function, without having to specify all of
its arguments. For example, suppose we want to add theming to a
ggplot
. One way to do that would be to extend the
scale_color_manual()
function and hard-code the official
Smith colors.
First, note that scale_color_manual()
takes the dots as
an argument.
args(ggplot2::scale_color_manual)
## function (..., values, aesthetics = "colour", breaks = waiver(),
## na.value = "grey50")
## NULL
Moreover, it passes the dots to manual_scale()
…
body(ggplot2::scale_color_manual)
## {
## manual_scale(aesthetics, values, breaks, ..., na.value = na.value)
## }
…which in turn passes the dots to discrete_scale()
…
body(ggplot2:::manual_scale)
## {
## if (is_missing(values)) {
## values <- NULL
## }
## else {
## force(values)
## }
## if (is.null(limits) && !is.null(names(values))) {
## limits <- function(x) intersect(x, names(values)) %||%
## character()
## }
## if (is.vector(values) && is.null(names(values)) && !is.waive(breaks) &&
## !is.null(breaks) && !is.function(breaks)) {
## if (length(breaks) <= length(values)) {
## names(values) <- breaks
## }
## else {
## names(values) <- breaks[1:length(values)]
## }
## }
## pal <- function(n) {
## if (n > length(values)) {
## cli::cli_abort("Insufficient values in manual scale. {n} needed but only {length(values)} provided.")
## }
## values
## }
## discrete_scale(aesthetic, "manual", pal, breaks = breaks,
## limits = limits, ...)
## }
…which actually does the work (Note that having 3 colons
:::
allows you to access functions in an R package that
aren’t @export
ed i.e. are kept hidden from the user in the
function’s roxygen2
code).
body(ggplot2::discrete_scale)
## {
## aesthetics <- standardise_aes_names(aesthetics)
## check_breaks_labels(breaks, labels)
## limits <- allow_lambda(limits)
## breaks <- allow_lambda(breaks)
## labels <- allow_lambda(labels)
## if (!is.function(limits) && (length(limits) > 0) && !is.discrete(limits)) {
## cli::cli_warn(c("Continuous limits supplied to discrete scale.",
## i = "Did you mean {.code limits = factor(...)} or {.fn scale_*_continuous}?"))
## }
## position <- arg_match0(position, c("left", "right", "top",
## "bottom"))
## if (is.null(breaks) && all(!is_position_aes(aesthetics))) {
## guide <- "none"
## }
## ggproto(NULL, super, call = match.call(), aesthetics = aesthetics,
## scale_name = scale_name, palette = palette, range = discrete_range(),
## limits = limits, na.value = na.value, na.translate = na.translate,
## expand = expand, name = name, breaks = breaks, labels = labels,
## drop = drop, guide = guide, position = position)
## }
According to the Smith College Visual Identity program, the official Smith colors are: #002855, #F2A900.
Thus, we can create a scale_color_smith()
function that
leverages scale_color_manual()
by hard-coding the official
Smith color values. But in order to get this to work, we have to
pass the dots so that scale_color_manual()
can do all the work that it normally does! Otherwise, we would have to
copy the source of scale_color_manual()
– but that would be
inefficient and brittle.
<- function(...) {
scale_color_smith ::scale_color_manual(
ggplot2
..., values = c("#002855", "#F2A900")
) }
We can then use our custom function. Note that since it only has two colors, it won’t work if it is mapped to a categorical variable with more than two levels.
ggplot(
filter(starwars, species %in% c("Human", "Droid")),
aes(x = height, y = mass, color = species)) +
geom_point() +
scale_color_smith()
## Filtering a tbl_df object with 87 rows...
## Warning: Removed 15 rows containing missing values (`geom_point()`).
Write the corresponding function scale_fill_smith()
and test it.
(Challenge) Write a scale_y_continuous_smith()
function that produces a gradient scale from Smith blue to Smith
gold.
The other main purpose of the dots is to avoid having to specify (or
even know) all the arguments that S3 methods take. For example, the
print()
function takes only the argument x
(the thing to be printed) and the dots ...
.
args(print)
## function (x, ...)
## NULL
However, different print()
methods take different
arguments.
args(print.data.frame)
## function (x, ..., digits = NULL, quote = FALSE, right = TRUE,
## row.names = TRUE, max = NULL)
## NULL
args(print.factor)
## function (x, quote = FALSE, max.levels = NULL, width = getOption("width"),
## ...)
## NULL
Note that the factor
method passes the dots to
print()
itself. In this case, what is being run is
print.default()
, which passes them to an internal
function.
body(print.factor)
## {
## ord <- is.ordered(x)
## if (length(x) == 0L)
## cat(if (ord)
## "ordered"
## else "factor", "(0)\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 <- if (is.null(max.levels))
## TRUE
## else max.levels
## 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)
## }
args(print.default)
## function (x, digits = NULL, quote = TRUE, na.print = NULL, print.gap = NULL,
## right = FALSE, max = NULL, width = NULL, useSource = TRUE,
## ...)
## NULL
body(print.default)
## {
## args <- pairlist(digits = digits, quote = quote, na.print = na.print,
## print.gap = print.gap, right = right, max = max, width = width,
## useSource = useSource, ...)
## missings <- c(missing(digits), missing(quote), missing(na.print),
## missing(print.gap), missing(right), missing(max), missing(width),
## missing(useSource))
## .Internal(print.default(x, args, missings))
## }
We will define our own print()
method, and then run it
on a factor
. First, let’s make a factor
.
<- factor(starwars$name)
x x
## [1] Luke Skywalker C-3PO R2-D2
## [4] Darth Vader Leia Organa Owen Lars
## [7] Beru Whitesun lars R5-D4 Biggs Darklighter
## [10] Obi-Wan Kenobi Anakin Skywalker Wilhuff Tarkin
## [13] Chewbacca Han Solo Greedo
## [16] Jabba Desilijic Tiure Wedge Antilles Jek Tono Porkins
## [19] Yoda Palpatine Boba Fett
## [22] IG-88 Bossk Lando Calrissian
## [25] Lobot Ackbar Mon Mothma
## [28] Arvel Crynyd Wicket Systri Warrick Nien Nunb
## [31] Qui-Gon Jinn Nute Gunray Finis Valorum
## [34] Jar Jar Binks Roos Tarpals Rugor Nass
## [37] Ric Olié Watto Sebulba
## [40] Quarsh Panaka Shmi Skywalker Darth Maul
## [43] Bib Fortuna Ayla Secura Dud Bolt
## [46] Gasgano Ben Quadinaros Mace Windu
## [49] Ki-Adi-Mundi Kit Fisto Eeth Koth
## [52] Adi Gallia Saesee Tiin Yarael Poof
## [55] Plo Koon Mas Amedda Gregar Typho
## [58] Cordé Cliegg Lars Poggle the Lesser
## [61] Luminara Unduli Barriss Offee Dormé
## [64] Dooku Bail Prestor Organa Jango Fett
## [67] Zam Wesell Dexter Jettster Lama Su
## [70] Taun We Jocasta Nu Ratts Tyerell
## [73] R4-P17 Wat Tambor San Hill
## [76] Shaak Ti Grievous Tarfful
## [79] Raymus Antilles Sly Moore Tion Medon
## [82] Finn Rey Poe Dameron
## [85] BB8 Captain Phasma Padmé Amidala
## 87 Levels: Ackbar Adi Gallia Anakin Skywalker Arvel Crynyd ... Zam Wesell
Note that max.levels
is one of the arguments to
print.factor()
, and so we can use that option.
args(print.factor)
## function (x, quote = FALSE, max.levels = NULL, width = getOption("width"),
## ...)
## NULL
print(x, max.levels = 3)
## [1] Luke Skywalker C-3PO R2-D2
## [4] Darth Vader Leia Organa Owen Lars
## [7] Beru Whitesun lars R5-D4 Biggs Darklighter
## [10] Obi-Wan Kenobi Anakin Skywalker Wilhuff Tarkin
## [13] Chewbacca Han Solo Greedo
## [16] Jabba Desilijic Tiure Wedge Antilles Jek Tono Porkins
## [19] Yoda Palpatine Boba Fett
## [22] IG-88 Bossk Lando Calrissian
## [25] Lobot Ackbar Mon Mothma
## [28] Arvel Crynyd Wicket Systri Warrick Nien Nunb
## [31] Qui-Gon Jinn Nute Gunray Finis Valorum
## [34] Jar Jar Binks Roos Tarpals Rugor Nass
## [37] Ric Olié Watto Sebulba
## [40] Quarsh Panaka Shmi Skywalker Darth Maul
## [43] Bib Fortuna Ayla Secura Dud Bolt
## [46] Gasgano Ben Quadinaros Mace Windu
## [49] Ki-Adi-Mundi Kit Fisto Eeth Koth
## [52] Adi Gallia Saesee Tiin Yarael Poof
## [55] Plo Koon Mas Amedda Gregar Typho
## [58] Cordé Cliegg Lars Poggle the Lesser
## [61] Luminara Unduli Barriss Offee Dormé
## [64] Dooku Bail Prestor Organa Jango Fett
## [67] Zam Wesell Dexter Jettster Lama Su
## [70] Taun We Jocasta Nu Ratts Tyerell
## [73] R4-P17 Wat Tambor San Hill
## [76] Shaak Ti Grievous Tarfful
## [79] Raymus Antilles Sly Moore Tion Medon
## [82] Finn Rey Poe Dameron
## [85] BB8 Captain Phasma Padmé Amidala
## 87 Levels: Ackbar Adi Gallia ... Zam Wesell
However, since print.factor()
eventually passes the dots
to print.default()
, and print.default()
accepts right
as one of its arguments, we can use the
right
argument with the print()
generic!
print(x, right = TRUE)
## [1] Luke Skywalker C-3PO R2-D2
## [4] Darth Vader Leia Organa Owen Lars
## [7] Beru Whitesun lars R5-D4 Biggs Darklighter
## [10] Obi-Wan Kenobi Anakin Skywalker Wilhuff Tarkin
## [13] Chewbacca Han Solo Greedo
## [16] Jabba Desilijic Tiure Wedge Antilles Jek Tono Porkins
## [19] Yoda Palpatine Boba Fett
## [22] IG-88 Bossk Lando Calrissian
## [25] Lobot Ackbar Mon Mothma
## [28] Arvel Crynyd Wicket Systri Warrick Nien Nunb
## [31] Qui-Gon Jinn Nute Gunray Finis Valorum
## [34] Jar Jar Binks Roos Tarpals Rugor Nass
## [37] Ric Olié Watto Sebulba
## [40] Quarsh Panaka Shmi Skywalker Darth Maul
## [43] Bib Fortuna Ayla Secura Dud Bolt
## [46] Gasgano Ben Quadinaros Mace Windu
## [49] Ki-Adi-Mundi Kit Fisto Eeth Koth
## [52] Adi Gallia Saesee Tiin Yarael Poof
## [55] Plo Koon Mas Amedda Gregar Typho
## [58] Cordé Cliegg Lars Poggle the Lesser
## [61] Luminara Unduli Barriss Offee Dormé
## [64] Dooku Bail Prestor Organa Jango Fett
## [67] Zam Wesell Dexter Jettster Lama Su
## [70] Taun We Jocasta Nu Ratts Tyerell
## [73] R4-P17 Wat Tambor San Hill
## [76] Shaak Ti Grievous Tarfful
## [79] Raymus Antilles Sly Moore Tion Medon
## [82] Finn Rey Poe Dameron
## [85] BB8 Captain Phasma Padmé Amidala
## 87 Levels: Ackbar Adi Gallia Anakin Skywalker Arvel Crynyd ... Zam Wesell
print.default()
through the dots. Do they work?Next, we will define a new print()
method for objects of
class my_factor
. This method displays the names of the
dots, gives an affirmation of your programming, and then calls and
returns print.factor()
. Thus, the behavior is the same as
print.factor()
, but with a little extra information.
<- function(x, ...) {
print.my_factor <- list(...)
dots
if (length(dots) > 0) {
message(paste("\nThe dots are:", names(dots)))
}
<- c(
affirmations "You are a really great programmer!",
"You are so good at this!",
"You're learning so much!",
"Keep trying and you will get there!"
)
if ("affirmation" %in% names(dots)) {
message(sample(affirmations, 1))
}
print.factor(x, ...)
}
class(x) <- "my_factor"
print(x, max.levels = 3, right = TRUE, affirmation = TRUE)
##
## The dots are: max.levels
## The dots are: right
## The dots are: affirmation
## You're learning so much!
## [1] 46 15 62 21 44 54 10 64 12 53 3 84 17 33 30 35 82 38 86 56 13 34 14 43 45
## [26] 1 50 4 83 51 61 52 27 37 69 70 68 81 73 60 75 20 11 5 25 29 9 48 40 41
## [51] 26 2 71 85 57 49 31 19 18 59 47 7 24 23 6 36 87 22 42 78 39 65 63 80 72
## [76] 74 32 77 66 76 79 28 67 58 8 16 55
## 87 Levels: Ackbar Adi Gallia ... Zam Wesell
print()
that works on
data.frame
s.Prompt: What else would you like to know about the dots?