# 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:

- Which geoms does this plot use?
- What variables are mapped to which aesthetics, and at which layer?
- Does this plot use `facet_wrap()`, and on which variable?

    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:

- `"global"` — set in `ggplot(aes(...))` only
- `"local"` — set in the layer only (e.g. `geom_point(aes(...))`)
- `"resolved"` — set in both; the local value takes precedence

    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