Shiny debugging and reprex guide

Shiny issues can be challenging to resolve relative to other problems with your code or statistical methods. Shiny apps are often large, complex projects with interacting files.

When seeking help from others it is considered polite to:

  • First, do your best to work through RStudio's debugging tools to diagnose your issue on your own. Often those shiny logs and tracebacks are useful to others trying to help out.
  • Second, strive to minimize the effort required to replicate your issue. You can do this with a reproducible example ("reprex").

Shiny Debugging

Errors in Shiny code can be difficult to track down. If you don't know where your problem is coming from, you can track it down with some of the techniques here:

Add browser() in strategic places. When R executes browser(), it will stop the application and let you run commands at the console to poke around and inspect values. For example, here is a browser() call inserted at the top of some plotting code:

output$plot <- renderPlot({
  browser()
  n <- input$n * 2
  plot(head(cars, n))
})

When R reaches the browser(), it will pause execution of the application and show you this at the console:

Called from: top level 
Browse[1]> 

You can inspect variables (like input$n), modify variables, and step through the code. When R is at the debug prompt, it supports the following special commands:

  • Type n then Enter: this runs the next line of code.
  • Type c then Enter: this continues normal execution and exits the debugger prompt.
  • Type s then Enter: this steps into the a function.

For more information about debugging R in general, see this chapter in Hadley Wickham's Advanced R book.

Also see the article on Debugging Shiny applications if you want to learn more about debugging Shiny applications in particular.

Creating Shiny reprexes

If you need to ask for help, you are much more likely to get help if you have a minimal reproducible example ("reprex") which other people can copy and paste to see the same problem. When the code is minimal, it makes it easy for others to see where the problem is. And if the problem can be reproduced by copying and pasting, it makes it much easier for others to debug the problem.

Here is an example of a reprex. If you copy and paste the code in an R console, the application will show an error: non-numeric argument to binary operator.

shinyApp(
  ui = fluidPage(
    selectInput("n", "N", 1:10),
    plotOutput("plot")
  ),
  server = function(input, output, session) {
    output$plot <- renderPlot({
      n <- input$n * 2
      plot(head(cars, n))
    })
  }
)

Because anyone can reproduce the problem by just copying and pasting the code, they can easily explore your code and test possible solutions.

How do I create a reprex from my application?

If you don't know what part of your code is triggering the problem, a good way to find it is to remove sections of code from your application, piece by piece, until the problem goes away. If removing a particular piece of code makes the problem stop, it's likely that that code is related to the problem. Once you've isolated the code that triggers the problem, you can go on to the next step.

To create a minimal example, you can go one of two ways: you can either keep removing code from your application until all the extraneous parts are gone, or you can construct a new Shiny application and copy the problem code into the new application. It's generally easier for others to understand the problem when you construct a new example, although either way is much better than not having a reproducible example at all.

If you want to construct a minimal example from scratch, you can start with this template of very basic application, which you can always find by running ?shinyApp (that shows the documentation for the function).

  shinyApp(
    ui = fluidPage(
      numericInput("n", "n", 1),
      plotOutput("plot")
    ),
    server = function(input, output, session) {
      output$plot <- renderPlot( plot(head(cars, input$n)) )
    }
  )

Start with the template and modify it by adding code that reproduces your problem. In many cases, the act of creating a reproducible example will reveal to you the cause of the problem, before you even ask for help.

Dealing with data

If your reproducible example uses a data set that is stored on your computer, it will be difficult for others to reproduce the problem, because they won't be able to just copy and paste your code; they'll have to download the data, run R in the correct directory, and so on.

You can make it easier for others by providing a data set in a way that can be copied and pasted. This can be either your original data set, or a simplified version of it that still causes the issue. For example, if you have a data frame with 1,000 rows, you might try to take the first 20 rows and see if that will still cause the problem. In general it is best to provide a simplified version of the data set, so that it is easy for others to understand it.

Option 1: Provide regular R code that generates a data set. For example:

mydata <- data.frame(x = 1:5, y = c("a", "b", "c", "d", "e"))

Option 2: Use dput(). If you have an object x, calling dput(x) will print out code that will generate an object just like x. For example, if you use dput(mydata), it will print the following:

> dput(mydata)
structure(list(x = 1:5, y = structure(1:5, .Label = c("a", "b", 
"c", "d", "e"), class = "factor")), class = "data.frame", row.names = c(NA, 
-5L))

If you run the structure(list(....)) code, it will return a data frame exactly like mydata. Once you have that code, you can put this in your application to generate mydata:

mydata <- structure(list(x = 1:5, y = structure(1:5, .Label = c("a", "b", 
"c", "d", "e"), class = "factor")), class = "data.frame", row.names = c(NA, 
-5L))

If dput() on your original data generates too much code, try taking a subset of your data and see if it will reproduce the problem.

Please bear in mind that dput() is not perfect, and cannot faithfully reproduce every kind of R object. It will, however, work for most objects, like data frames and vectors.

Option 3: If your data set can't be put in your code in a way that can be copied and pasted, you might have to just upload it so that others can download it. It is best to avoid this if possible.

Personal and Private Information

Users should not share personal or protected information in topic threads or reprex data.
What if you need to share contact info or an ip, or discuss data for a reprex related to a protected dataset? What if you see a violation of this policy? Check out Personally Identifiable & Protected Information Guide guide and our Privacy Policy

11 Likes

I'm replying instead of editing as I'm not sure how to state this in the guide:

One pattern I see is learners have R that's not working and they have it all wrapped up in a Shiny app. I am of the opinion that the best way to get a working Shiny app is to get the basic logic working in R then (and only then) wrap the shiny bits around it. How can we tactfully (or not tactfully) encourage this and help a learner understand that by trying to debug basic R logic inside of a Shiny app is a very slow and hard way to learn R?

14 Likes

Based on some sustainer discussion, some think it would be useful to have a series of guides, (and much of this exists already, though perhaps less geared toward the novice level of shiny-users seeking help here and elsewhere)

  1. Shiny debugging,
  2. Shiny reprex, (suggesting rstudio.cloud to host code)
  3. one for trouble-shooting deployment issues.
  4. Guidance about testing core logic outside of shiny (and how to mock things effectively to do so) and generally encouraging people to get the basic engine working in plain R before compliexifying things with shiny
3 Likes