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: geom
s control the look, stat
s control the data aspects
stat_identity
instead of geom_point
Every 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 polygon
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, ...)
)
}
you want to …
Define defaults
geom_polygon
Change 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/