blog banner with white text "Caching Interactive Elements in Shiny" and Shiny open source logo and a performance dashboard graphic

R Shiny Caching – Top 3 Ways to Cache Interactive Elements in R Shiny


Are your Shiny dashboards getting slow? Maybe it’s time to explore some R Shiny caching options. We’ve been there – a client wants dozens of charts on a single dashboard page, and wants it running smoothly. It’s easier said than done, especially if there’s a lot of data to show.

Are you a newcomer to R and R Shiny? Here’s how you can make a career out of it.

Today we’ll explore the top 3 methods of R Shiny caching to increase the speed and responsiveness of your dashboards. These are:


renderCachedPlot() – Plot Output with Cached Images

The renderCachedPlot() function renders a reactive plot with plot images cached to disk. The function has many arguments, but there are two you must know:

  • expr – expression that generates a plot, but doesn’t take reactive dependencies as renderPlot() function does. It is re-executed only when the cache key changes.
  • cacheKeyExpr – an expression that upon evaluation returns an object which will be serialized and hashed using the digest() function to generate a string that will be used as a cache key. If the cache key is the same as the previous time, it assumes the plot is the same and can be retrieved from the cache.

When it comes to cache scoping, there are again multiple options. You can share cache across multiple sessions (cache = "app") which is the default behavior, or you can limit caching to a single session (cache = "session"). Either way, the cache will be 10 MB in size and will be stored in memory, using a memoryCache object.

To change any of these settings, you can call shinyOptions() at the top of the file, as you’ll see shortly.

Looking to speed up your R Shiny app? Jump in the fast lane with our definitive guide to speeding up R Shiny.

Let’s see how to implement R Shiny caching with the renderCachedPlot() function. The code snippet below shows you how to make an entire dataset as a part of the cache key. For reference, the example was taken from the official documentation page.

library(shiny)
shinyOptions(cache = cachem::cache_disk("./myapp-cache"))

mydata <- reactiveVal(data.frame(x = rnorm(400), y = rnorm(400)))


ui <- fluidPage(
    sidebarLayout(
    sidebarPanel(
        sliderInput("n", "Number of points", 50, 400, 100, step = 50),
        actionButton("newdata", "New data")
    ),
    mainPanel(
        plotOutput("plot")
    )
    )
)


server <- function(input, output, session) {
    observeEvent(input$newdata, {
    mydata(data.frame(x = rnorm(400), y = rnorm(400)))
    })

    output$plot <- renderCachedPlot({
        Sys.sleep(2)
        d <- mydata()
        seqn <- seq_len(input$n)
        plot(d$x[seqn], d$y[seqn], xlim = range(d$x), ylim = range(d$y))
    },
    cacheKeyExpr = {
        list(input$n, mydata())
    }
    )
}

shinyApp(ui = ui, server = server)

Once you launch the app, you’ll see the following:

Image 1 - Basic R Shiny app that uses renderCachedPlot

Image 1 – Basic R Shiny app that uses renderCachedPlot

At the surface level, everything looks normal. But what R does behind the surface is save the cache files to the myapp-cache directory:

Image 2 - Contents of the myapp-cache directory

Image 2 – Contents of the myapp-cache directory

And that’s how you can cache the contents of the plot. Let’s explore another R Shiny caching option.

bindCache() – Easily Speed Up Dashboards with R Shiny Caching

The bindCache() function adds caching to reactive() expression and render functions. It requires one or more expressions that are used to generate a cache key, which is used to determine if a computation has occurred before and can be retrieved from the cache.

By default, bindCache() shares a cache with all user sessions connected to the application. It’s also possible to scope the cache to a session, just as we’ve seen in the previous section. The whole thing, at least setup-wise, works pretty much the same as the first option explored today.

Need to improve your Shiny dashboards? Explore Appsilon’s open-source packages to level up your performance and design.

We’ll demonstrate how bindCache() works by examining an example from rdrr.io. In the most simple words, the example allows the user to specify two numbers with sliders and perform multiplication with a button.

The result is then displayed below. There’s nothing computationally expensive going on, but R sleeps for two seconds after the action button is clicked.

This is where R Shiny caching comes in. It caches the results for two given numbers, so each time you multiply the same numbers the calculation is done immediately:

library(shiny)
shinyOptions(cache = cachem::cache_disk("./bind-cache"))

ui <- fluidPage(
    sliderInput("x", "x", 1, 10, 5),
    sliderInput("y", "y", 1, 10, 5),
    actionButton("go", "Go"),
    div("x * y: "),
    verbatimTextOutput("txt")
)


server <- function(input, output, session) {
    r <- reactive({
        message("Doing expensive computation...")
        Sys.sleep(2)
        input$x * input$y
    }) %>%
        bindCache(input$x, input$y) %>%
        bindEvent(input$go)
        output$txt <- renderText(r())
}

shinyApp(ui = ui, server = server)

Here’s what the corresponding Shiny app looks like:

Image 3 - R Shiny app that uses bindCache()

Image 3 – R Shiny app that uses bindCache()

As before, cached results are saved to a folder on disk – but this time, to a folder named bind-cache. Here are the contents after a couple of calculations:

Image 4 - Contents of the bind-cache directory

Image 4 – Contents of the bind-cache directory

Easy, right? Let’s see how our last caching option for R Shiny works.

memoise Package – Cache results of R Functions

The memoise R package is used for the so-called memoisation of functions. In plain English, it caches the results of a function so that when you call it again with the same arguments it returns the previously computed value.

To get started, you’ll first have to install this R package:

install.packages("memoise")

The approach to caching is a bit different than before. You’ll first want to specify where the cached files will be saved. We’ve used the cache_filesystem() function for the task, but there are others available. Then, you’ll want to write your R functions, followed by a call to memoise() with two arguments – your R function and the location where cached files should be stored.

From there, the story is pretty much identical to before. We’ve slightly modified the R Shiny app from the previous section – it’s now a dashboard with a sidebar layout and a bit more verbose declaration of elements and their parameters:

library(shiny)
library(memoise)
cd <- cache_filesystem("./r-memoise")


multiply_nums <- function(x, y) {
    Sys.sleep(2)
    return (x * y)
}
mmn <- memoise(multiply_nums, cache = cd)

ui <- fluidPage(
    sidebarLayout(
    sidebarPanel(
        sliderInput(inputId = "x", label = "X", min = 1, max = 10, value = 5),
        sliderInput(inputId = "y", label = "Y", min = 1, max = 10, value = 5),
        actionButton(inputId = "calculate", label = "Multiply")
    ),
    mainPanel(
        div("X * Y = "),
        verbatimTextOutput("txt")
    ),
    fluid = TRUE
    )
)

server <- function(input, output, session) {
    r <- eventReactive(input$calculate, {
        mmn(x = input$x, y = input$y)
    })
    output$txt <- renderText(r())
}

shinyApp(ui = ui, server = server)

Once launched, the R Shiny app looks as follows:

Image 5 - R Shiny app that uses the memoise package

Image 5 – R Shiny app that uses the memoise package

Give it a go and multiply a couple of numbers. As before, if you were to repeat a calculation, ergo call the mmn() function which calls multiply_nums() function with previously seen arguments, your results will be fetched from the cache.

As expected, caching results are saved to the folder:

Image 6 - Contents of the r-memoise directory

Image 6 – Contents of the r-memoise directory

And that’s how you can use the memoise R package to cache results of R function, all wrapped in R Shiny. Let’s wrap things up next.


Summary of R Shiny Caching

Today you’ve seen three basic examples of how R Shiny caching works. Truth be told, we’ve only scratched the surface, but it’s just enough to get you started. Diving deeper would require a dedicated article for each caching option, which is something we might do in the future.

If you’d like us to cover a specific caching option in detail, let us know in the comments.

In the meantime, don’t hesitate to share your favorite R Shiny caching package/option in the comment section below. Also, we’d love to see the types of dashboards you can build with R Shiny and caching, so don’t hesitate to share your results on Twitter – @appsilon.

Ready to monitor user adoption in R Shiny? Shiny.stats, Shinylogs, and Google Analytics hold the answer.