Extracting Plot Specifications with ggspec

Introduction

ggspec provides a family of spec_*() functions that inspect a ggplot2 object and return its declarative specification as tidy data frames. This makes it easy to answer questions like:

library(ggspec)
library(ggplot2)

A motivating example

p <- ggplot(mpg, aes(displ, hwy)) +
  geom_point(aes(colour = class)) +
  geom_smooth(method = "lm", se = FALSE) +
  facet_wrap(~drv) +
  scale_colour_brewer(palette = "Set1") +
  labs(title = "Engine displacement vs highway MPG",
       x     = "Displacement (L)",
       y     = "Highway MPG (hwy)")
p
#> `geom_smooth()` using formula = 'y ~ x'

Layer specification

spec_layers() returns one row per layer, with columns for the geom, stat, position adjustment, and resolved aesthetic mappings.

spec_layers(p)
#> # A tibble: 3 × 8
#>   layer geom   stat     position mapping   params           inherit_aes data_id
#>   <int> <chr>  <chr>    <chr>    <list>    <list>           <lgl>         <int>
#> 1     0 <NA>   <NA>     <NA>     <chr [2]> <list [0]>       NA                1
#> 2     1 point  identity identity <chr [3]> <named list [2]> TRUE             NA
#> 3     2 smooth smooth   identity <chr [2]> <named list [7]> TRUE             NA

The mapping column is a list-column of named character vectors, where each name is an aesthetic and each value is the variable mapped to it.

# Inspect the mapping for layer 1 (geom_point)
spec_layers(p)$mapping[[1]]
#>       x       y 
#> "displ"   "hwy"

Controlling inheritance

By default (inherit = "resolve"), global mappings set in ggplot() are merged with layer-local mappings, with the local mapping taking precedence for any aesthetic defined in both.

# inherit = FALSE: only mappings explicitly set on the layer
spec_layers(p, inherit = FALSE)$mapping
#> [[1]]
#>       x       y 
#> "displ"   "hwy" 
#> 
#> [[2]]
#>  colour 
#> "class" 
#> 
#> [[3]]
#> named character(0)

Aesthetic specification (long format)

spec_aes() pivots the mapping information into long format — one row per (layer-aesthetic) pair — making it easy to filter and inspect with standard dplyr operations.

spec_aes(p)
#> # A tibble: 7 × 5
#>   layer geom   aesthetic variable source
#>   <int> <chr>  <chr>     <chr>    <chr> 
#> 1     0 <NA>   x         displ    global
#> 2     0 <NA>   y         hwy      global
#> 3     1 point  x         displ    global
#> 4     1 point  y         hwy      global
#> 5     1 point  colour    class    local 
#> 6     2 smooth x         displ    global
#> 7     2 smooth y         hwy      global

The source column distinguishes:

library(dplyr)
spec_aes(p) |>
  filter(aesthetic == "colour")
#> # A tibble: 1 × 5
#>   layer geom  aesthetic variable source
#>   <int> <chr> <chr>     <chr>    <chr> 
#> 1     1 point colour    class    local

Scale specification

spec_scales() lists only explicitly added scales (default scales inferred at render time are not included, as they do not exist in the object before rendering).

spec_scales(p)
#> # A tibble: 1 × 6
#>   aesthetic scale_class   scale_type name  transform guide 
#>   <chr>     <chr>         <chr>      <chr> <chr>     <chr> 
#> 1 colour    ScaleDiscrete discrete   <NA>  <NA>      legend

Facet specification

spec_facets() returns a single-row data frame describing the faceting.

spec_facets(p)
#> # A tibble: 1 × 6
#>   facet_type rows  cols  scales space labeller       
#>   <chr>      <chr> <chr> <chr>  <chr> <chr>          
#> 1 wrap       <NA>  drv   fixed  <NA>  params$labeller

For an unfaceted plot, facet_type is "null".

Label specification

spec_labels(p)
#> # A tibble: 3 × 2
#>   aesthetic label                             
#>   <chr>     <chr>                             
#> 1 x         Displacement (L)                  
#> 2 y         Highway MPG (hwy)                 
#> 3 title     Engine displacement vs highway MPG

Coordinate system specification

spec_coord(p)
#> # A tibble: 1 × 5
#>   coord_type xlim   ylim   expand clip 
#>   <chr>      <list> <list> <lgl>  <chr>
#> 1 cartesian  <NULL> <NULL> TRUE   on

# Flipped coordinates
spec_coord(p + coord_flip())
#> # A tibble: 1 × 5
#>   coord_type xlim   ylim   expand clip 
#>   <chr>      <list> <list> <lgl>  <chr>
#> 1 flip       <NULL> <NULL> TRUE   on

The master summary: spec_plot()

spec_plot() joins all of the above into a single wide data frame with one row per layer. Scale, facet, coordinate, and label information are embedded as list-columns, so all plot information is reachable from a single object.

sp <- spec_plot(p)
sp
#> # A tibble: 3 × 14
#>   layer geom   stat   position mapping params       inherit_aes data_id aes_long
#>   <int> <chr>  <chr>  <chr>    <list>  <list>       <lgl>         <int> <list>  
#> 1     0 <NA>   <NA>   <NA>     <chr>   <list [0]>   NA                1 <tibble>
#> 2     1 point  ident… identity <chr>   <named list> TRUE             NA <tibble>
#> 3     2 smooth smooth identity <chr>   <named list> TRUE             NA <tibble>
#> # ℹ 5 more variables: datasets <list>, scales <list>, facets <list>,
#> #   coord <list>, labels <list>

# Access per-layer aesthetics
sp$aes_long[[1]]
#> # A tibble: 2 × 5
#>   layer geom  aesthetic variable source
#>   <int> <chr> <chr>     <chr>    <chr> 
#> 1     0 <NA>  x         displ    global
#> 2     0 <NA>  y         hwy      global

# Access the facet spec (same in every row)
sp$facets[[1]]
#> # A tibble: 1 × 6
#>   facet_type rows  cols  scales space labeller       
#>   <chr>      <chr> <chr> <chr>  <chr> <chr>          
#> 1 wrap       <NA>  drv   fixed  <NA>  params$labeller