%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Inconsolata'}}}%% flowchart LR subgraph Function["Function Scope"] Running["Function executes"] --> Browser("<code>browser()</code>") Browser --> Debugger("View variables/values") subgraph Browse["Interactive Debugger"] Debugger --> Step["Execute line by line"] end Step --> Exit("Exit <code>browser()</code>") Exit --> Exec["Execution resumes"] end style Debugger fill:#4CB7DB,stroke:none,rx:10,ry:10 style Exit fill:#4CBB9D,stroke:none,rx:10,ry:10 style Step fill:#4CB7DB,stroke:none,rx:10,ry:10 style Running fill:none,stroke:none,rx:10,ry:10 style Browse fill:#F8F8FA,stroke:#333,stroke-width:1px,rx:5,ry:5 style Browser fill:#4CBB9D,stroke:none,rx:10,ry:10 style Function fill:#E5E6EB style Exec fill:none,stroke:none,rx:10,ry:10
10 Debuggers
Bugs can cause our app to crash, produce incorrect results or displays, and result in other unexpected behaviors. Fixing bugs in our app-package is an important step to ensure our application continues behaving as expected. Using an interactive debugger can help us find the root cause of the error.
Debugging in RStudio is covered elsewhere,1 so we’ll focus on debugging our Shiny app code using Positron’s interactive debugger.
10.1 Debugging with browser()
Interactive debugging (e.g., using browser()
or setting a breakpoint) allows us to ‘peek inside’ a function’s scope to view intermediate values/variables and break down the execution line by line.
Unfortunately, using browser()
and breakpoints are not as straightforward within reactive contexts, because browser()
can disrupt the reactive flow, making maintaining an interactive debugging session challenging.
10.1.1 Reactive browsing
In a Shiny context, we want to pause execution without altering or stopping the reactive flow. Fortunately, Shiny already has a function that performs this: observe()
.
Within a Shiny server()
function, any call to observe()
creates a reactive observer that ‘listens’ for changes to reactive dependencies (and runs any enclosed code whenever those dependencies change).
When browser()
is called from within the observe()
scope, the code execution pauses and temporarily suspends the reactive flow so we can inspect the environment (without altering or stopping the reactive flow).
Interactive Debugger & Shiny Apps
Placing a call to browser()
inside observe()
will trigger the interactive debugger when the observer is invalidated.
observe({
browser()
})
This allows us to interactively examine variables and reactive expressions (within the scope of the observe()
function).
10.2 Example: ggplot2movies
app
In the Resources we developed an application using the ggplot2movies
data.2 In the following sections we’re going to use the interactive debugger to see the inner workings of the ‘Remove missing’ checkbox.
We’ll begin by placing the observe()
and browser()
breakpoint inside the module server function containing the ‘Remove missing’ input (right after the moduleServer()
function). We’ll close the observe()
scope after the reactive inputs()
are created:
show/hide debugging functions in the module server function
<- function(id, var_inputs) {
dev_mod_scatter_server moduleServer(id, function(input, output, session) {
observe({
browser()
<- fst::read_fst("tidy_movies.fst")
all_data
<- reactive({
graph_data if (input$missing) {
::drop_na(data = all_data)
tidyrelse {
}
all_data
}|>
}) bindEvent(input$missing)
<- reactive({
inputs <- tools::toTitleCase(var_inputs()$plot_title)
plot_title list(
x = var_inputs()$x,
y = var_inputs()$y,
z = var_inputs()$z,
alpha = var_inputs()$alpha,
size = var_inputs()$size,
plot_title = plot_title
)
})
})
observe({
$scatterplot <- renderPlot({
output<- sap::scatter_plot(
plot df = graph_data(),
x_var = inputs()$x,
y_var = inputs()$y,
col_var = inputs()$z,
alpha_var = inputs()$alpha,
size_var = inputs()$size
)+
plot ::labs(
ggplot2title = inputs()$plot_title,
x = stringr::str_replace_all(
::toTitleCase(inputs()$x), "_", " "),
toolsy = stringr::str_replace_all(
::toTitleCase(inputs()$y), "_", " ")
tools+
) ::theme_minimal() +
ggplot2::theme(legend.position = "bottom")
ggplot2
})|>
}) bindEvent(graph_data(), inputs())
}) }
- 1
-
Observer scope
- 2
-
Call to
browser()
(execution paused) - 3
-
Read
tidy_movies.fst
data
- 4
-
Missing data checkbox logic
- 5
- Reactive values from user inputs
- 6
-
Module graph code (outside of
observe()
scope)
Then we’ll load the changes:
Ctrl/Cmd + Shift + L
And launch the app using:
ggp2_launch_app(options = list(test.mode = TRUE))
Launch app with the shinypak
package:
launch('10_debugger')
As noted above, browser()
pauses code execution and activates the interactive debugger mode, allowing us to view objects, execute code, and ‘step through’ the function line-by-line.
10.2.1 IDE Changes
When the app is launched, Positron alerts us that we’re in debugging mode by making a few changes to the IDE:
- The Run and Debug sidebar menu item is displayed and the footer is highlighted in blue3
- The
dev_mod_scatter.R
file in the Editor highlights the call tobrowser()
in yellow
- The Console displays the interactive debugger prompt:
Browse[1]>
observe()
does not inherently pause or interrupt other reactive processes—it just triggers when changes occur within its scope. So when we’re using observe()
for debugging, we need to define the context (or scope) for its behavior.
The output in the Console (shown above) tells us the tidy_movies.fst
data are downloaded, but our placement suspends the execution of the application before these data are loaded and the graph is rendered in the UI (shown below).
The interactive debugger can only access variables and values inside the observe()
scope, but this process can be incredibly useful for addressing bugs (and for exploring how an application works). In the next sections, we’ll ‘step through’ the module function to explore how the missing values are removed from the graph.
10.2.2 Variables and values
We want to use the interactive debugger to proceed through the module function until the data object enters the logic for the missing checkbox, and then we can confirm its structure.
Step through/execute each line of code by entering n
in the Console.
Browse[1]> n
As we ‘step through’ the function, Positron’s Console displays the debug at
location, followed by the code line number:
In the Editor, the same line of code is highlighted in yellow:
The line creating graph_data
gives us a hint for how the missing data are removed (i.e., with bindEvent()
), but we’ll explore this more in Print debugging.
Under Locals in the DEBUG VARIABLES sidebar, we can see all_data
is listed as a <data.frame>
, and graph_data
are listed as a <reactive.event>
:
10.2.3 Inspecting variables
We can use the Console to evaluate code while the interactive debugger is running. This comes in handy if we want to check the structure of an object inside a module (like all_data
).
1]> str(all_data) Browse[
'data.frame': 58788 obs. of 10 variables:
$ title : chr "$" "$1000 a Touchdown" ...
$ year : int 1971 1939 1941 1996 1975 ...
$ length : int 121 71 7 70 71 91 93 25 97 ...
$ budget : int NA NA NA NA NA NA NA NA NA ...
$ rating : num 6.4 6 8.2 8.2 3.4 4.3 5.3 ...
$ votes : int 348 20 5 6 17 45 200 24 18 51 ...
$ mpaa : Factor w/ 5 levels "G","PG","PG-13" ...
$ genre_count: int 2 1 2 1 0 1 2 2 1 0 ...
$ genres : chr "Comedy, Drama" "Comedy" ...
$ genre : Factor w/ 8 levels "Action": 6 3 6 ...
This gives us an idea of the total rows before missing are removed.
10.2.4 Inspecting values
The reactive values and inputs can also be viewed in the Console, and we can see graph_data()
is ‘bound’ to input$missing
with bindEvent()
. We can confirm the input$missing
value in the Console:
1]> input$missing Browse[
[1] TRUE
This tells us the ‘Remove missing’ checkbox has been selected, and we can verify the missing values have been removed from graph_data()
:
1]> identical(
Browse[nrow(tidyr::drop_na(all_data)),
nrow(graph_data())
)
[1] TRUE
Using browser()
to ‘step through’ an application gives us a better understanding of the ‘order of execution’, and it lets us see how input$missing
and bindEvent()
work together to remove the missing values with the checkbox.
Recap
For an introduction to the IDE’s debugging tools, see Debugging with the RStudio IDE. Debugging is also covered in Advanced R, 2ed and Mastering Shiny.↩︎
You can refresh your memory on the
ggplot2movies
application in Section 9.3.↩︎Previous versions of Positron highlighted the footer in red.↩︎