--- title: "Frequently asked questions" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Frequently asked questions} %\VignetteEncoding{UTF-8} %\VignetteEngine{knitr::rmarkdown} editor_options: chunk_output_type: console --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} set.seed(2021) library(ggtrace) ``` ## Is ggtrace() safe? `ggtrace()` is essentially just a wrapper around `base::trace()` designed to make it easy and safe to programmatically trace/untrace functions and methods. So the short answer is that `ggtrace()` is at least as safe as `trace()`. But how safe _is_ `trace()`? The beauty of `trace()` is that the modified function being traced _masks_ over the original function without _overwriting_ it. This allows for **non-destructive modifications to the execution behavior**. In this simple example, we add a trace to `replace()` which multiples the value of `x` by 10 as the function enters the last step. ```{r} body(replace) replace(1:5, 3, 30) as.list(body(replace)) trace(replace, tracer = quote(x <- x * 10), at = 3) ``` The traced function looks strange and runs with a different behavior ```{r} class(replace) body(replace) replace(1:5, 3, 30) ``` But again, this is non-destructive. The original function body is safely stored away in the `"original"` attribute of the traced function ```{r} attr(replace, "original") ``` The original function can be recovered by removing the trace with a call to `untrace()` ```{r} untrace(replace) body(replace) replace(1:5, 3, 30) ``` Beyond this, `{ggtrace}` also offers some extra built-in safety measures: - Cleans up after itself by untracing on exit (the default behavior with `once = TRUE`) - Always untraces before tracing, which prevents nested traces from being created - Provides ample messages about whethere there is an existing trace (which you can also check with `is_traced()`) - Exits as early as possible if the method expression is ill-formed with more informative error messages that you can actually act on - Prevents traces from being created on functions that aren't bound to a variable in some way (i.e., prevents you from creating traces that you can't trigger) However, some expression you pass to `ggtrace()` for delayed evaluation are not without consequences. You need to be careful about running functions that have _side effects_ and making _assignments to environments_ (ex: `self$method <- ...` will modify in place). But this isn't a problem of `{ggtrace}` - they follow from the general rules of reference semantics in R. ## What can you `ggtrace()`? ### Functions {base} functions ```{r} sample(letters, 5) ggtrace(sample, 1, quote(x <- LETTERS), verbose = FALSE) sample(letters, 5) sample(letters, 5) ``` Imported functions ```{r} ggtrace(ggplot2::mean_se, 1, quote(cat("Running...\n")), verbose = FALSE) ggplot2::mean_se(mtcars$mpg) ``` Custom functions ```{r} please_return_number <- function() { result <- runif(1) result } please_return_number() ggtrace(please_return_number, -1, quote(result <- "no"), verbose = FALSE) please_return_number() ``` ### ggproto methods ```{r} library(ggplot2) boxplot_plot <- ggplot(mpg, aes(drv, hwy)) + geom_boxplot() boxplot_plot ``` Default tracing behavior with untracing on exit ```{r, fig.show='hide'} ggtrace(StatBoxplot$compute_group, -1, verbose = FALSE) # Plot not printed to save space boxplot_plot last_ggtrace() ``` Persistent trace with `once = FALSE` and explicit untracing with `gguntrace()` ```{r fig.show='hide'} global_ggtrace_state() global_ggtrace_state(TRUE) clear_global_ggtrace() ggtrace(StatBoxplot$compute_group, -1, once = FALSE, verbose = FALSE) # Plot not printed to save space boxplot_plot gguntrace(StatBoxplot$compute_group) global_ggtrace() global_ggtrace_state(FALSE) ``` ### S3/S4 generics The exported generic function `ggplot_build()` from `{ggplot2}` is itself not very meaningful, but the unexported method for the `` class `ggplot_build.ggplot()` contains the actual data transformation pipeline. ```{r} body(ggplot_build) attr(utils::methods("ggplot_build"), "info") ``` You can trace the `ggplot_build()` method defined for `` in the same way as functions ```{r} ggtrace(ggplot2:::ggplot_build.ggplot, -1, verbose = FALSE) boxplot_plot ``` ```{r} last_ggtrace()[[1]]$data[[1]] identical(last_ggtrace()[[1]]$data[[1]], layer_data(boxplot_plot, 1)) ``` ### R6 methods Adopted from [Advanced R Ch. 14.2](https://adv-r.hadley.nz/r6.html) ```{r} library(R6) Accumulator <- R6Class("Accumulator", list( sum = 0, add = function(x = 1) { self$sum <- self$sum + x invisible(self) }) ) x <- Accumulator$new() x$add(1) x$sum ``` ```{r} ggtrace( method = x$add, trace_steps = c(1, -1), trace_exprs = list( before = quote(self$sum), after = quote(self$sum) ), once = FALSE, verbose = FALSE ) x$add(10) last_ggtrace() x$add(100) last_ggtrace() gguntrace(x$add) x$add(1000) x$sum ``` ## What can't you `ggtrace()`? - Non-functions (ex: constants, object properties). But you can still inspect the values for these with `ggbody()` - Functions not defined in an environment (ex: you can't define a function to trace on-the-spot inside `ggtrace()`) - Limited support for closures (the LHS of the `$` must itself be an environment where the function can be searched for) ## How can I save a modified ggplot? When you trace the internals of ggplot, that doesn't directly modify the _instructions_ for plotting. Instead, it changes how certain components behave when they are _executed_. This means that you will not get a different ggplot with the following code if `original_plot` is being traced with modifications, since `original_plot` is not being executed here. ```{r} original_plot <- ggplot(mtcars, aes(hp, mpg)) + geom_point() ggtrace(ggplot2:::ggplot_build.ggplot, -1, quote(data[[1]]$colour <- "red"), verbose = FALSE) modified_plot <- original_plot ``` It looks like it worked when you first print it... ```{r} modified_plot ``` But the variable `modified_pot` doesn't hold modified _code_ for generating the plot. Instead, it just happened to trigger the trace on ggplot_build.ggplot(). So the next time it runs, it's ran with the normal behavior of `original_plot`. ```{r} modified_plot ``` To capture the actual figure generated by a ggplot, you can use `ggplotGrob()`, which returns the **Gr**aphical **ob**ject representation of the plot: ```{r} ggtrace(ggplot2:::ggplot_build.ggplot, -1, quote(data[[1]]$colour <- "red"), verbose = FALSE) modified_plot <- ggplotGrob(original_plot) modified_plot ``` What you get is an object of class ``, which you can draw to your device like any other grob: ```{r} class(modified_plot) library(grid) grid.newpage() grid.draw(modified_plot) ``` You can also use ggsave() to render a `` to an image: ```{r, eval = FALSE} # Not ran ggsave(filename = "modified_plot.png", plot = modified_plot, ...) ``` Still, `modified_plot` is only the graphical representation of the plot and not itself a ggplot object so you can't keep adding layers to it. So grobs are more limiting in that sense. But it's not totally limiting like a raster image of a figure. For example, `{patchwork}` has `patchwork::wrap_ggplot_grob()` which allows a `` to be properly aligned to other ggplots. ```{r} library(patchwork) original_plot_titled <- original_plot + ggtitle("original plot") # Panels get aligned since `modified_plot` contains info about that original_plot_titled + wrap_ggplot_grob(modified_plot) ```