University of Nebraska-Lincoln
2024-12-04
Quick review of the ggplot2 layering system
Examples
More examples
ggplot2 is …pretty wildly used (more than 1 million downloads each month)
based on the Grammar of Graphics, i.e conceptually sound
supports a layering system
very flexible with (relatively) good defaults
References:
mappings (aes): data variables are mapped to graphical elements
layers: geometric elements (geoms, such as points, lines, rectangles, text, …) and statistical transformations (stats, are identity, counts, bins, …)
scales: map values in the data space to values in an aesthetic space (e.g. color, size, shape, but also position)
coordinate system (coord): defaults to Cartesian, but pie charts use e.g. polar coordinates
facetting: for small multiples (subsets) and their arrangement
theme: defaults to theme_grey fine-tune display items, such as font and its size, color of background, margins, …
Usually only need data, mapping with aes and one geom:

from ggplot2 vignette on extensions
Making a convex hull: Object definition
stat_chull <- function(
mapping = NULL, data = NULL, geom = "polygon",
position = "identity", na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE, ...) {
layer(
stat = StatChull, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend,
inherit.aes = inherit.aes, params = list(na.rm = na.rm, ...)
)
}Every geom has a (default) stat
function (mapping = NULL, data = NULL, stat = "identity", position = "identity",
..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
{
layer(data = data, mapping = mapping, stat = stat, geom = GeomPoint,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list2(na.rm = na.rm, ...))
}
<bytecode: 0x7fe8d441f230>
<environment: namespace:ggplot2>
Each function provides access to a different aspect in the layer: geoms control the look, stats control the data aspects
stat_identity instead of geom_pointEvery extension starts at the geom/stat level
ggplot2 is expecting a Geom and a Stat specification for every layer
But: you don’t need to (and can not) start from scratch
Two prototype objects: ggplot2::Geom and ggplot2::Stat
Geom Object<ggproto object: Class Geom, gg>
aesthetics: function
default_aes: uneval
draw_group: function
draw_key: function
draw_layer: function
draw_panel: function
extra_params: na.rm
handle_na: function
non_missing_aes:
optional_aes:
parameters: function
rename_size: FALSE
required_aes:
setup_data: function
setup_params: function
use_defaults: function
Stat Object<ggproto object: Class Stat, gg>
aesthetics: function
compute_group: function
compute_layer: function
compute_panel: function
default_aes: uneval
dropped_aes:
extra_params: na.rm
finish_layer: function
non_missing_aes:
optional_aes:
parameters: function
required_aes:
retransform: TRUE
setup_data: function
setup_params: function
Rely on the defaults: pick the Geom/Stat that is closest to what you want to do, and expand
Make minimal changes otherwise
Specifies required mappings, and compute_group
StatChull and polygonstat_chull <- function(
mapping = NULL, data = NULL, geom = "polygon",
position = "identity", na.rm = FALSE,
show.legend = NA, inherit.aes = TRUE, ...) {
layer(
stat = StatChull, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend,
inherit.aes = inherit.aes, params = list(na.rm = na.rm, ...)
)
}you want to …
Define defaults
geom_polygonChange stat to chull, and GeomPolygon to GeomChull.
Everything else stays the same
geom_chull <- function (mapping = NULL, data = NULL,
stat = "chull", position = "identity",
rule = "evenodd", ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE)
{
layer(data = data, mapping = mapping, stat = stat,
geom = GeomChull, position = position,
show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, rule = rule, ...))
}GeomChull <- ggproto(
"GeomChull", GeomPolygon,
default_aes = ggplot2::aes(
colour = "grey30", fill = "grey50", alpha = 0.5, # new ones
linewidth=0.5, linetype = 1, subgroup=NULL,
size = 3, shape = 19, stroke = 0.5 # for the points
),
draw_panel = function(..., self = self) {
GeomPolygon$draw_panel(..., self)
}
)GeomChull <- ggproto(
"GeomChull", GeomPolygon,
default_aes = ggplot2::aes(
colour = "grey30", fill = "grey50", alpha = 0.5, # new ones
linewidth=0.5, linetype = 1,
size = 3, shape = 19, stroke = 0.5 # for the points
),
draw_panel = function(..., self = self) {
# using the two layers together
grid::grobTree(
GeomPolygon$draw_panel(..., self),
GeomPoint$draw_panel(..., self)
)
}
)Why does this not draw separate convex hulls for each group?
lvplot packageMaking a new chart: letter value (box)plots are a suggestion by JW Tukey in Exploratory Data Analysis (~1980)
Instead of just doing a box for Quartiles, the next set of \(2^{-k}\) quantiles are included (called the Fourth, the Eighths, D, C, B, A, Z, …)
Implements pairs geom_lv, GeomLv, and stat_lv, StatLv
Besides implementing a geom and a stat - what else is needed?
How about this one?
Look at more code!
Listing of ‘official’ extension packages: https://exts.ggplot2.tidyverse.org/gallery/
ggrepel package: https://github.com/slowkow/ggrepel
ggpcp package: https://heike.github.io/ggpcp/