| Title: | Extract and Compare 'ggplot2' Plot Specifications as Tidy Data Frames |
|---|---|
| Description: | Inspects 'ggplot' objects by extracting their full declarative specification - layers, aesthetic mappings, scales, facets, coordinate systems, and labels - as tidy data frames. A second tier of functions enables structural comparison of two 'ggplot' objects, supporting automated plot testing, auditing, and framework-agnostic grading workflows. Unlike 'ggcheck', which is designed exclusively for 'learnr'/'gradethis' pipelines and returns ad-hoc objects, 'ggspec' returns rectangular, pipeable output and does not require any grading framework as a dependency. |
| Authors: | Clement Lee [aut, cre] (ORCID: <https://orcid.org/0000-0003-1785-8671>) |
| Maintainer: | Clement Lee <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.0 |
| Built: | 2026-05-28 14:55:37 UTC |
| Source: | https://github.com/cran/ggspec |
Stops with an informative error if p is not a ggplot object.
assert_ggplot(p, arg_name = deparse(substitute(p)))assert_ggplot(p, arg_name = deparse(substitute(p)))
p |
An object to check. |
arg_name |
Character string used in the error message to name the
argument. Defaults to the deparsed expression of |
p invisibly, if it passes the check.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) assert_ggplot(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) assert_ggplot(p)
Normalises a ggplot specification (extracted via spec_plot()) into a
standard form, resolving multiple representations of the same intent.
The result is an ggspec_canon object that carries the canonicalised
specification, a log of every change made, and the original specification
for comparison.
canon(x, mode = "structural")canon(x, mode = "structural")
x |
A ggplot object, a |
mode |
Character scalar controlling which normalisation rules are
applied.
Visual and conceptual comparison (which require rendering via
|
canon() is idempotent: canon(canon(x)) produces the same
specification as canon(x), and the second call records zero changes.
canon() is transparent: x$changes is a regular tibble listing
every normalisation applied, with columns rule, dimension,
layer, from, and to.
An object of class ggspec_canon, a named list with components:
specA spec_plot() tibble after canonicalisation.
changesA tibble with columns rule (chr), dimension (chr),
layer (int or NA), from (chr), to (chr). Zero rows means
the input was already in canonical form.
modeThe mode string used.
originalThe spec_plot() tibble before canonicalisation.
# Layer ordering is canonical in structural mode p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth() + ggplot2::geom_point() c1 <- canon(p1) c1$changes # records the layer reordering c1$spec$geom # layer 0 NA, then alphabetical: "point", "smooth" # Idempotency c2 <- canon(c1) nrow(c2$changes) # 0 — already canonical# Layer ordering is canonical in structural mode p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth() + ggplot2::geom_point() c1 <- canon(p1) c1$changes # records the layer reordering c1$spec$geom # layer 0 NA, then alphabetical: "point", "smooth" # Idempotency c2 <- canon(c1) nrow(c2$changes) # 0 — already canonical
Compares a student/observed plot against a reference plot and calls
fail_fn if any check fails. By default fail_fn = stop, so the function
works in any R context. Swap in gradethis::fail() for learnr grading or a
testthat expectation for unit testing.
check_plot( p, expected, check = NULL, mode = NULL, fail_fn = stop, pass_fn = invisible, ... )check_plot( p, expected, check = NULL, mode = NULL, fail_fn = stop, pass_fn = invisible, ... )
p |
The observed ggplot object (e.g., the student's plot). |
expected |
The reference ggplot object. |
check |
Character vector of checks to run; passed to |
mode |
Character scalar: comparison mode. If |
fail_fn |
A function called with the failure message string when the
check does not pass. Defaults to |
pass_fn |
A function called with the success message string when all
checks pass. Defaults to |
... |
Additional arguments passed to |
The ggspec_result invisibly. Side effects (calling fail_fn or
pass_fn) are the primary interface.
ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs_correct <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs_wrong <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_line() # Passes silently (direct comparison) check_plot(obs_correct, ref, check = "layers") # Passes silently (structural mode: layer order ignored) obs_reordered <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() check_plot(obs_reordered, ref, check = "layers", mode = "structural") # Calls stop() with a message tryCatch( check_plot(obs_wrong, ref, check = "layers"), error = function(e) message("Caught: ", conditionMessage(e)) )ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs_correct <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs_wrong <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_line() # Passes silently (direct comparison) check_plot(obs_correct, ref, check = "layers") # Passes silently (structural mode: layer order ignored) obs_reordered <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() check_plot(obs_reordered, ref, check = "layers", mode = "structural") # Calls stop() with a message tryCatch( check_plot(obs_wrong, ref, check = "layers"), error = function(e) message("Caught: ", conditionMessage(e)) )
compare_conceptual() checks whether two plots communicate the same
information using potentially different visual encodings. It runs a
sequence of bespoke detectors; if any detector fires, the plots are
declared conceptually similar. If none fires, it falls back to
compare_visual().
compare_conceptual(p1, p2)compare_conceptual(p1, p2)
p1 |
The reference ggplot object. |
p2 |
The observed ggplot object. |
Conceptual similarity is always qualified by a WHEN condition, which is
described in the $message field of the returned object.
Called internally by compare_plots() when mode = "conceptual".
A ggspec_compare object.
library(ggplot2) p_box <- ggplot(mpg, aes(x = class, y = hwy)) + geom_boxplot() p_viol <- ggplot(mpg, aes(x = class, y = hwy)) + geom_violin() compare_conceptual(p_box, p_viol) # TRUElibrary(ggplot2) p_box <- ggplot(mpg, aes(x = class, y = hwy)) + geom_boxplot() p_viol <- ggplot(mpg, aes(x = class, y = hwy)) + geom_violin() compare_conceptual(p_box, p_viol) # TRUE
compare_plots() is the high-level entry point for comparing two ggplot
objects. The mode argument selects which equivalence pathway to use:
compare_plots(p1, p2, mode = "structural", check = NULL, ...)compare_plots(p1, p2, mode = "structural", check = NULL, ...)
p1 |
The reference ggplot object. |
p2 |
The observed ggplot object to compare against |
mode |
One of |
check |
Character vector of checks to run. For structural modes:
any of |
... |
Additional arguments passed to individual |
"strict" / "structural": spec-level comparison via canon().
Plots that are structurally equivalent after canonicalisation
(different data/mapping placement, layer order, geom_col vs
geom_bar(stat="identity")) are detected as equal.
"visual": rendered-output comparison via compare_visual().
Uses ggplot2::ggplot_build() rather than spec inspection; detects
equivalences that structural comparison cannot (e.g. pre-computed vs
stat-based geoms, coord_flip() with swapped aesthetics).
"conceptual": communicative-intent comparison via
compare_conceptual(). Detects plots that convey the same information
using different visual encodings (e.g. histogram vs density).
equiv_plot() is equivalent to compare_plots(mode = "strict").
A ggspec_compare object (extends ggspec_result) with the usual
$pass, $message, and $detail fields, plus $mode. For structural
modes, also contains $canon_p1 and $canon_p2.
# Global vs per-layer data placement: fails at strict, passes at structural library(ggplot2) p1 <- ggplot(mpg, aes(displ, hwy)) + geom_point() p2 <- ggplot(mpg) + geom_point(aes(displ, hwy)) as.logical(compare_plots(p1, p2, mode = "strict")) # FALSE as.logical(compare_plots(p1, p2, mode = "structural")) # TRUE # Visual equivalence: geom_bar vs geom_col on pre-counted data library(dplyr) pb <- ggplot(mpg, aes(x = class)) + geom_bar() pc <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() as.logical(compare_plots(pb, pc, mode = "structural")) # FALSE as.logical(compare_plots(pb, pc, mode = "visual")) # TRUE# Global vs per-layer data placement: fails at strict, passes at structural library(ggplot2) p1 <- ggplot(mpg, aes(displ, hwy)) + geom_point() p2 <- ggplot(mpg) + geom_point(aes(displ, hwy)) as.logical(compare_plots(p1, p2, mode = "strict")) # FALSE as.logical(compare_plots(p1, p2, mode = "structural")) # TRUE # Visual equivalence: geom_bar vs geom_col on pre-counted data library(dplyr) pb <- ggplot(mpg, aes(x = class)) + geom_bar() pc <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() as.logical(compare_plots(pb, pc, mode = "structural")) # FALSE as.logical(compare_plots(pb, pc, mode = "visual")) # TRUE
compare_visual() checks whether two plots produce identical rendered
output. It uses ggplot2::ggplot_build() for data comparison rather than spec
inspection, making it capable of detecting equivalences that structural
comparison cannot (e.g. pre-computed vs stat-based geoms, coord_flip()
with swapped aesthetics).
compare_visual(p1, p2, check = c("rendered", "labels", "facets", "coord"))compare_visual(p1, p2, check = c("rendered", "labels", "facets", "coord"))
p1 |
The reference ggplot object. |
p2 |
The observed ggplot object. |
check |
Character vector of checks to run. Options:
|
Called internally by compare_plots() when mode = "visual".
A ggspec_compare object.
library(ggplot2); library(dplyr) p1 <- ggplot(mpg, aes(x = class)) + geom_bar() p2 <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() compare_visual(p1, p2)library(ggplot2); library(dplyr) p1 <- ggplot(mpg, aes(x = class)) + geom_bar() p2 <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() compare_visual(p1, p2)
Calls ggplot2::ggplot_build() and compares the result against the declared
spec to classify each parameter and aesthetic as explicit (set by the
user) or default (filled in by ggplot2).
enrich_spec(p)enrich_spec(p)
p |
A ggplot object. |
The three-way classification follows directly from the spec/build comparison:
explicit — quantity appears in both spec and build (user-specified).
default — quantity is in build but absent from spec, or its value matches the ggplot2 default (ggplot2 filled it in).
transformed — quantity is in spec but its build value differs
(e.g. a colour name resolved to a hex code by a scale); detectable by
comparing built_aes$value with the original aesthetic variable.
How explicit detection works:
ggplot2 stores all parameters — both user-supplied and defaulted — in
layer$geom_params / layer$stat_params. To distinguish them, enrich_spec()
constructs a reference layer (via geom_*() with no arguments) and compares
each stored value against the reference default. A value that differs from
its default is classed as explicit; one that matches is classed as default.
The one edge case this cannot detect is a user explicitly setting a param to
its own default value (e.g. geom_smooth(se = TRUE) when TRUE is the
default) — such cases are conservatively reported as default.
Aesthetic constants set via fixed values (e.g. geom_point(colour = "red"))
are always reported as explicit regardless of value.
A tibble::tibble() extending spec_layers() with two additional
list-columns:
params_tblOne row per non-aesthetic parameter stored in the
layer. Columns: param (chr), value (list), explicit (lgl),
source (chr — "geom", "stat", or "aes").
explicit = TRUE means the value differs from the ggplot2 default (or
is a fixed aesthetic constant); explicit = FALSE means ggplot2 would
have used the same value without any user input.
built_aesOne row per aesthetic resolved during build. Columns:
aesthetic (chr), value (list), explicit (lgl).
explicit = TRUE means the aesthetic was mapped or set as a constant
by the user; explicit = FALSE means ggplot2 applied a default
(e.g. colour = "black" for geom_point() when no colour mapping is
present).
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) es <- enrich_spec(p) # Which params are explicit vs default for layer 1? es$params_tbl[[1]] # Which aesthetics are explicit vs default for layer 1? es$built_aes[[1]]p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) es <- enrich_spec(p) # Which params are explicit vs default for layer 1? es$params_tbl[[1]] # Which aesthetics are explicit vs default for layer 1? es$built_aes[[1]]
Compare aesthetic mappings of two ggplot objects
equiv_aes(p1, p2, layer = NULL, exact = FALSE)equiv_aes(p1, p2, layer = NULL, exact = FALSE)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
layer |
Integer vector of layer indices to compare. |
exact |
Logical. If |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() equiv_aes(p1, p2) # passes (p1's mappings are a subset) equiv_aes(p1, p2, exact = TRUE) # fails (p2 has extra colour mapping)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() equiv_aes(p1, p2) # passes (p1's mappings are a subset) equiv_aes(p1, p2, exact = TRUE) # fails (p2 has extra colour mapping)
Compare coordinate systems of two ggplot objects
equiv_coord(p1, p2)equiv_coord(p1, p2)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- p1 + ggplot2::coord_flip() equiv_coord(p1, p2)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- p1 + ggplot2::coord_flip() equiv_coord(p1, p2)
Compares by hashing (using rlang::hash()), so large data frames are
handled efficiently. Column order is ignored; row order is ignored.
Only works with raw ggplot objects (not ggspec_canon).
equiv_data(p1, p2, layer = NULL)equiv_data(p1, p2, layer = NULL)
p1 |
Reference ggplot object. |
p2 |
Observed ggplot object. |
layer |
Integer vector of layer indices to compare. |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- ggplot2::ggplot( ggplot2::mpg[ggplot2::mpg$class == "suv", ], ggplot2::aes(displ, hwy) ) + ggplot2::geom_point() equiv_data(p1, p2)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- ggplot2::ggplot( ggplot2::mpg[ggplot2::mpg$class == "suv", ], ggplot2::aes(displ, hwy) ) + ggplot2::geom_point() equiv_data(p1, p2)
Compare facet specification of two ggplot objects
equiv_facets(p1, p2)equiv_facets(p1, p2)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~class) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~drv) equiv_facets(p1, p2)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~class) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~drv) equiv_facets(p1, p2)
Compare labels of two ggplot objects
equiv_labels(p1, p2, aesthetics = NULL)equiv_labels(p1, p2, aesthetics = NULL)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
aesthetics |
Character vector of label names to compare. |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::labs(title = "My plot") p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::labs(title = "Different title") equiv_labels(p1, p2) equiv_labels(p1, p2, aesthetics = "x") # passes (x labels same)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::labs(title = "My plot") p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::labs(title = "Different title") equiv_labels(p1, p2) equiv_labels(p1, p2, aesthetics = "x") # passes (x labels same)
Compare layer structure of two ggplot objects
equiv_layers(p1, p2, exact = FALSE, check_order = FALSE)equiv_layers(p1, p2, exact = FALSE, check_order = FALSE)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
exact |
Logical. If |
check_order |
Logical. If |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- p1 + ggplot2::geom_smooth() equiv_layers(p1, p2) # passes (p1's layers are a subset of p2) equiv_layers(p1, p2, exact = TRUE) # fails (p2 has more layers)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() p2 <- p1 + ggplot2::geom_smooth() equiv_layers(p1, p2) # passes (p1's layers are a subset of p2) equiv_layers(p1, p2, exact = TRUE) # fails (p2 has more layers)
Compare parameters of a specific layer in two ggplot objects
equiv_params(p1, p2, layer = 1L, params = NULL)equiv_params(p1, p2, layer = 1L, params = NULL)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
layer |
Integer: which layer index to compare (1-based). Compared by layer value (not row position). |
params |
Character vector of parameter names to check. |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth(method = "lm", se = FALSE) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth(method = "loess", se = TRUE) equiv_params(p1, p2, layer = 1L, params = c("se"))p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth(method = "lm", se = FALSE) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_smooth(method = "loess", se = TRUE) equiv_params(p1, p2, layer = 1L, params = c("se"))
equiv_plot() is equivalent to compare_plots(mode = "strict") — it
performs a direct structural comparison with no canonicalisation beyond
null-normalisation. Plots that differ only in where data or mapping is
specified (global vs per-layer) will fail equiv_plot() but may pass
compare_plots() with mode = "structural" or looser.
equiv_plot( p1, p2, check = c("layers", "aes", "scales", "facets", "labels", "coord"), ... )equiv_plot( p1, p2, check = c("layers", "aes", "scales", "facets", "labels", "coord"), ... )
p1 |
The reference ggplot object. |
p2 |
The observed ggplot object to compare against |
check |
Character vector of checks to run. |
... |
Additional arguments passed to individual |
A ggspec_result object. as.logical() on the result gives a
single TRUE/FALSE.
ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::geom_smooth() equiv_plot(ref, obs) equiv_plot(ref, obs, check = "layers")ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::geom_smooth() equiv_plot(ref, obs) equiv_plot(ref, obs, check = "layers")
Calls ggplot2::ggplot_build() on both inputs and compares the resulting
panel data layer by layer. Useful for detecting visual equivalence between
plots that differ structurally (e.g. geom_bar on raw data vs geom_col
on pre-counted data).
equiv_rendered(p1, p2)equiv_rendered(p1, p2)
p1 |
The reference ggplot object. |
p2 |
The observed ggplot object. |
Comparison is restricted to the key visual columns (position, size, colour, fill, alpha, group, PANEL) that are common to both layers. Rows are sorted by the first shared column before comparison.
A ggspec_result.
library(ggplot2) library(dplyr) p1 <- ggplot(mpg, aes(x = class)) + geom_bar() p2 <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() equiv_rendered(p1, p2) # TRUElibrary(ggplot2) library(dplyr) p1 <- ggplot(mpg, aes(x = class)) + geom_bar() p2 <- mpg |> count(class) |> ggplot(aes(x = class, y = n)) + geom_col() equiv_rendered(p1, p2) # TRUE
Compare scales of two ggplot objects
equiv_scales(p1, p2, aesthetics = NULL)equiv_scales(p1, p2, aesthetics = NULL)
p1 |
Reference ggplot or |
p2 |
Observed ggplot or |
aesthetics |
Character vector of aesthetics to compare. |
A ggspec_result.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::scale_x_log10() p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() equiv_scales(p1, p2)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::scale_x_log10() p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() equiv_scales(p1, p2)
Wraps check_plot() as a testthat expectation. Requires the testthat
package (listed in Suggests).
expect_equiv_plot( p, expected, check = c("layers", "aes", "scales", "facets", "labels", "coord"), ... )expect_equiv_plot( p, expected, check = c("layers", "aes", "scales", "facets", "labels", "coord"), ... )
p |
The observed ggplot object. |
expected |
The reference ggplot object. |
check |
Character vector of checks to run; passed to |
... |
Additional arguments passed to |
Called for its side effects (a testthat expectation).
if (requireNamespace("testthat", quietly = TRUE)) { # Inside a testthat test: testthat::test_that("plot has correct layers", { ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() expect_equiv_plot(obs, ref, check = "layers") }) }if (requireNamespace("testthat", quietly = TRUE)) { # Inside a testthat test: testthat::test_that("plot has correct layers", { ref <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() obs <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() expect_equiv_plot(obs, ref, check = "layers") }) }
Test whether a plot contains a given layer
has_layer(p, geom = NULL, stat = NULL)has_layer(p, geom = NULL, stat = NULL)
p |
A ggplot object. |
geom |
Optional character string: geom suffix to look for (e.g.
|
stat |
Optional character string: stat suffix to look for (e.g.
|
TRUE if at least one layer matches all supplied criteria.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() has_layer(p, geom = "point") has_layer(p, geom = "smooth")p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() has_layer(p, geom = "point") has_layer(p, geom = "smooth")
Test whether an object is a ggplot
is_ggplot(p)is_ggplot(p)
p |
An object to test. |
TRUE if p inherits from "gg", FALSE otherwise.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) is_ggplot(p) is_ggplot(list())p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) is_ggplot(p) is_ggplot(list())
Convenience predicate wrapping. Returns TRUE if any
layer (or the global mapping) maps aesthetic to variable.
mapping_exists(p, aesthetic, variable)mapping_exists(p, aesthetic, variable)
p |
A ggplot object. |
aesthetic |
Character scalar: name of the aesthetic, e.g. |
variable |
Character scalar: name of the variable, e.g. |
TRUE or FALSE.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) mapping_exists(p, "colour", "class") mapping_exists(p, "colour", "drv")p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) mapping_exists(p, "colour", "class") mapping_exists(p, "colour", "drv")
Count the number of layers in a ggplot
n_layers(p)n_layers(p)
p |
A ggplot object. |
A non-negative integer.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::geom_smooth() n_layers(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::geom_smooth() n_layers(p)
Returns one row per (layer-aesthetic) pair, making it easy to check
which variable is mapped to which aesthetic in which layer. A row with
layer = 0 is always included to document any mapping specified directly
in ggplot().
spec_aes(p, layer = NULL, inherit = "resolve")spec_aes(p, layer = NULL, inherit = "resolve")
p |
A ggplot object. |
layer |
Integer vector of layer indices to include. |
inherit |
Controls global/local mapping inheritance for non-zero
layers; same semantics as in |
A tibble::tibble() with one row per layer-aesthetic and columns:
layerInteger layer index. 0 = global context; 1..N =
regular layers.
geomGeom name for the layer. NA for layer 0.
aestheticAesthetic name, e.g. "x", "colour".
variableVariable label mapped to the aesthetic (as a string).
sourceWhere the mapping originates: "global" (layer-0
rows), "local" (layer-specific only), or "resolved" (present in
both global and local, local takes precedence).
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth() spec_aes(p) spec_aes(p, layer = 1L)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth() spec_aes(p) spec_aes(p, layer = 1L)
Returns a single-row data frame describing the plot's coordinate system.
spec_coord(p)spec_coord(p)
p |
A ggplot object. |
A tibble::tibble() with one row and columns:
coord_typeShort name of the coordinate system, e.g.
"cartesian", "flip", "polar", "sf".
xlimList-column: numeric vector of x limits, or NULL if not
set.
ylimList-column: numeric vector of y limits, or NULL if not
set.
expandLogical: whether axes are expanded beyond the data range.
clipWhether clipping is applied: "on", "off", or
NA.
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() spec_coord(p1) p2 <- p1 + ggplot2::coord_flip() spec_coord(p2) p3 <- p1 + ggplot2::coord_polar() spec_coord(p3)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() spec_coord(p1) p2 <- p1 + ggplot2::coord_flip() spec_coord(p2) p3 <- p1 + ggplot2::coord_polar() spec_coord(p3)
Returns one row per unique data frame used in the plot — either at the
global level (ggplot(data, ...)) or attached to individual layers
(geom_*(data = ...)). Datasets are deduplicated by their rlang::hash().
spec_data(p)spec_data(p)
p |
A ggplot object. |
A tibble::tibble() with columns:
data_idInteger: sequential identifier starting at 1.
labelCharacter: "hash_<first8chars>" of the data frame's
hash. The original symbol or expression is unavailable after
evaluation; the hash is unique and consistent across calls.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() spec_data(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() spec_data(p)
Returns a single-row data frame describing the plot's faceting. When no
faceting is applied, facet_type is "null" and all other columns are
NA.
spec_facets(p)spec_facets(p)
p |
A ggplot object. |
A tibble::tibble() with one row and columns:
facet_typeOne of "null", "wrap", or "grid".
rowsFor facet_grid(): character representation of the row
faceting variable(s). NA otherwise.
colsCharacter representation of the column faceting
variable(s). For facet_wrap() this holds the wrap variable(s).
scalesScale freedom: "fixed", "free", "free_x", or
"free_y".
spaceSpace freedom (grid only): "fixed", "free", etc.
labellerLabeller as a string, e.g. "label_value".
p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~class) spec_facets(p1) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_grid(drv ~ cyl) spec_facets(p2)p1 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_wrap(~class) spec_facets(p1) p2 <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point() + ggplot2::facet_grid(drv ~ cyl) spec_facets(p2)
Returns one row per label currently set on the plot (title, subtitle, caption, tag, and any aesthetic labels such as x, y, colour, fill, etc.).
spec_labels(p)spec_labels(p)
p |
A ggplot object. |
A tibble::tibble() with one row per label and columns:
aestheticThe name of the label slot, e.g. "title", "x",
"colour".
labelThe label string. NA if the slot is NULL or a
waiver().
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() + ggplot2::labs(title = "Engine vs highway MPG", x = "Displacement (L)") spec_labels(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() + ggplot2::labs(title = "Engine vs highway MPG", x = "Displacement (L)") spec_labels(p)
Returns one row per layer in the plot (including a row 0 for the global context), with columns describing the geom, stat, position adjustment, aesthetic mappings, non-aesthetic parameters, and data source.
spec_layers(p, inherit = "resolve")spec_layers(p, inherit = "resolve")
p |
A ggplot object. |
inherit |
Controls how global and local aesthetic mappings are combined
in the
|
A tibble::tibble() with one row per layer (plus a row 0 for the
global context) and columns:
layerInteger layer index. 0 = global context; 1..N =
regular layers (1-based).
geomGeom name, e.g. "point", "smooth". NA for layer 0.
statStat name, e.g. "identity", "smooth". NA for
layer 0.
positionPosition adjustment name. NA for layer 0.
mappingList-column of named character vectors: aesthetic to variable label.
paramsList-column of named lists of non-aesthetic layer parameters. Empty list for layer 0.
inherit_aesLogical: does the layer inherit the global
mapping? NA for layer 0.
data_idInteger: identifier of the dataset used, referencing
spec_data(). NA for layers that inherit the global data.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth(method = "lm", se = FALSE) spec_layers(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth(method = "lm", se = FALSE) spec_layers(p)
Calls all spec_*() functions and joins their results into a single
wide-format data frame with one row per layer (including a row 0 for the
global context). Scale, facet, coordinate, and label information are
repeated across all layer rows.
spec_plot(p, inherit = "resolve")spec_plot(p, inherit = "resolve")
p |
A ggplot object. |
inherit |
Passed to |
A tibble::tibble() with one row per layer (plus layer 0) and
columns from spec_layers() plus the following additional list-columns:
aes_longList-column: the spec_aes() data frame for that
layer (for convenient access without re-calling spec_aes()).
datasetsList-column: the spec_data() data frame (repeated
for every row).
scalesList-column: the full spec_scales() data frame,
repeated for each layer.
facetsList-column: the spec_facets() data frame, repeated
for each layer.
coordList-column: the spec_coord() data frame, repeated
for each layer.
labelsList-column: the spec_labels() data frame, repeated
for each layer.
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth(method = "lm") + ggplot2::facet_wrap(~drv) + ggplot2::labs(title = "Engine vs MPG") spec_plot(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy)) + ggplot2::geom_point(ggplot2::aes(colour = class)) + ggplot2::geom_smooth(method = "lm") + ggplot2::facet_wrap(~drv) + ggplot2::labs(title = "Engine vs MPG") spec_plot(p)
Returns one row per explicitly added scale. Scales that were never added
by the user (i.e. default scales inferred at render time) are not included
because they do not exist in the ggplot object before ggplot2::ggplot_build() is
called; use spec_plot() to access built scales.
spec_scales(p)spec_scales(p)
p |
A ggplot object. |
A tibble::tibble() with one row per scale and columns:
aestheticThe aesthetic the scale controls, e.g. "x",
"colour".
scale_classFull S3 class string of the scale object.
scale_typeShort type label, e.g. "continuous",
"discrete", "binned", "manual", "identity".
nameScale name / axis label (waiver() reported as NA).
transformName of the transformation applied (continuous scales
only; NA otherwise).
guideGuide type as a string, e.g. "legend", "colourbar",
"none".
p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() + ggplot2::scale_colour_brewer(palette = "Set1") + ggplot2::scale_x_log10() spec_scales(p)p <- ggplot2::ggplot(ggplot2::mpg, ggplot2::aes(displ, hwy, colour = class)) + ggplot2::geom_point() + ggplot2::scale_colour_brewer(palette = "Set1") + ggplot2::scale_x_log10() spec_scales(p)