{ggplot2}
internals with {ggtrace}
June Choe
University of Pennsylvania
“outliers”
“quartiles”
“outliers”
“quartiles”
{ggtrace}
Let’s interact with ggplot internals!
{dplyr}
can get you very far!{ggtrace}
featuresGives users finer control over the plots they make
Helps developers debug custom internal functions
{ggplot2}
internals
Stat
Stat
Geom
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{ggplot2}
Stat and Geom methodsStatistical transformation (compute)
Stat$compute_layer()
Stat$compute_panel()
Stat$compute_group()
Geometric transformation (draw)
Geom$draw_layer()
Geom$draw_panel()
Geom$draw_group()
{dplyr}
all the way down
{dplyr}
all the way down
ggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
{dplyr}
all the way downggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
_layer()
functions called once per layer
{dplyr}
all the way downggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
_panel()
functions called once per panel (a.k.a. “facet”)
{dplyr}
all the way downggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
_group()
functions called once per group
ggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
What might we want to do with this beast of a pipeline?
ggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
# <-- What does the data for 2nd layer, 3rd panel look like at this point?
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
ggplot_output <- data_input %>%
... # aesthetic mappings, x-y scale transformations, etc.
group_by(layer) %>%
mutate( ... ) %>%
group_by(panel) %>%
mutate( ... ) %>%
group_by(group) %>%
mutate( ... ) %>%
summarize( ... ) %>%
my_fun() %>% # <-- What consequence does this have on the pipeline?
mutate( ... ) %>%
mutate( ... ) %>%
mutate( ... ) %>%
... # constructing plot layout with {grid} and {gtable}, etc.
They are essentially functions, but look weird:
[1] "ggproto_method"
[1] "closure"
You can’t do any useful things with them interactively:
Familiar debugging tools (ex: debug()
) fail out of the box
Introducing {ggtrace}
{ggtrace}
Toolkit to Inspect, Capture, and Highjack the internals
Workflow functions ggtrace_{action}_{value}()
:
x
: The ggplot object
method
: The ggproto method
cond
: When to interact with the method1
“While rendering x
, interact with method
when cond
is met”
Inspect
workflow
compute_layer()
compute_layer()
compute_layer()
compute_layer()
# A tibble: 342 × 5
fill x y PANEL group
<fct> <mppd_dsc> <dbl> <fct> <int>
1 Adelie 1 181 1 1
2 Adelie 1 186 1 1
3 Adelie 1 195 1 1
# … with 339 more rows
ymin lower middle upper ymax outliers notchupper notchlower x width relvarwidth flipped_aes fill PANEL group
1 174 186 190 195 208 172, 210 191.1572 188.8428 1 0.7 12.288206 FALSE Adelie 1 1
2 178 191 196 201 212 197.9160 194.0840 2 0.7 8.246211 FALSE Chinstrap 1 2
3 203 212 216 221 231 217.2822 214.7178 3 0.7 11.090537 FALSE Gentoo 1 3
Stat
split-apply-combine designStat
split-apply-combine inputs fill x y PANEL group
1 Adelie 1 181 1 1
2 Adelie 1 186 1 1
fill x y PANEL group
275 Chinstrap 2 192 1 2
276 Chinstrap 2 196 1 2
Stat
split-apply-combine outputs ymin lower middle upper ymax outliers notchupper notchlower x width relvarwidth flipped_aes
1 174 186 190 195 208 172, 210 191.1572 188.8428 1 0.7 12.28821 FALSE
ymin lower middle upper ymax outliers notchupper notchlower x width relvarwidth flipped_aes
1 178 191 196 201 212 197.916 194.084 2 0.7 8.246211 FALSE
ymin lower middle upper ymax outliers notchupper notchlower x width relvarwidth flipped_aes fill PANEL group
1 174 186 190 195 208 172, 210 191.1572 188.8428 1 0.7 12.288206 FALSE Adelie 1 1
2 178 191 196 201 212 197.9160 194.0840 2 0.7 8.246211 FALSE Chinstrap 1 2
3 203 212 216 221 231 217.2822 214.7178 3 0.7 11.090537 FALSE Gentoo 1 3
Capture
workflow
compute_group()
Highjack
workflow
returnValue()
draw_group()
fill ymin lower middle upper ymax outliers notchupper notchlower x flipped_aes PANEL group
1 #F8766D 174 186 190 195 208 172, 210 191.1572 188.8428 1 FALSE 1 1
ymin_final ymax_final xmin xmax xid newx new_width weight colour size alpha shape linetype
1 172 210 0.65 1.35 1 1 0.7 1 grey20 0.5 NA 19 solid
What can we do with these "grob"
s?
draw_group()
draw_group()
draw_group()
gTree
s - a collection of grob
sdraw_group()
draw_group()
draw_group()
- hide{grid}
power usersggtrace_highjack_return(p + facet_wrap(~ year), Geom$draw_panel, cond = TRUE,
value = quote({
y_pos <- .25 * ._counter_ #<- internal counter tracking nth time the method is called
grobTree( circleGrob(y = y_pos, gp = gpar(fill = linearGradient())), # R >= 4.1
editGrob(returnValue(), vp = viewport(clip = circleGrob(y = y_pos))) ) }))
Workflow functions also work for:
Other ggprotos, unexported functions, S3/S4 methods
Functions and methods from extension packages
Beyond workflow functions, {ggtrace}
also provides:
Interactive debugging with ggedit()
and ggdebug()
Low-level control with ggtrace()
and with_ggtrace()
https://yjunechoe.github.io/ggtrace/
https://yjunechoe.github.io/ggtrace-user2022