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. <blockquote>Are you a newcomer to R and R Shiny? Here's how you can make a career out of <a href="https://appsilon.com/how-to-start-a-career-as-an-r-shiny-developer/" target="_blank" rel="noopener">R Shiny development</a>.</blockquote> Today we'll explore the top 3 methods of R Shiny caching to increase the speed and responsiveness of your dashboards. These are: <ul><li><a href="#rendercachedplot">renderCachedPlot() - Plot Output with Cached Images</a></li><li><a href="#bindcache">bindCache() - Easily Speed Up Dashboards with R Shiny Caching</a></li><li><a href="#memoise">memoise Package - Cache results of R Functions</a></li></ul> <hr /> <h2 id="rendercachedplot">renderCachedPlot() - Plot Output with Cached Images</h2> The <code>renderCachedPlot()</code> function renders a reactive plot with plot images cached to disk. The function has many arguments, but there are two you must know: <ul><li><code>expr</code> - expression that generates a plot, but doesn't take reactive dependencies as <code>renderPlot()</code> function does. It is re-executed only when the cache key changes.</li><li><code>cacheKeyExpr</code> - an expression that upon evaluation returns an object which will be serialized and hashed using the <code>digest()</code> 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.</li></ul> When it comes to cache scoping, there are again multiple options. You can share cache across multiple sessions (<code>cache = "app"</code>) which is the default behavior, or you can limit caching to a single session (<code>cache = "session"</code>). Either way, the cache will be 10 MB in size and will be stored in memory, using a <code>memoryCache</code> object. To change any of these settings, you can call <code>shinyOptions()</code> at the top of the file, as you'll see shortly. <blockquote>Looking to speed up your R Shiny app? Jump in the fast lane with our <a href="https://appsilon.com/speeding-up-r-shiny/" target="_blank" rel="noopener">definitive guide to speeding up R Shiny</a>.</blockquote> Let's see how to implement R Shiny caching with the <code>renderCachedPlot()</code> 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 <a href="https://shiny.rstudio.com/reference/shiny/1.2.0/renderCachedPlot.html" target="_blank" rel="nofollow noopener">official documentation page</a>. <pre><code class="language-r">library(shiny) shinyOptions(cache = cachem::cache_disk("./myapp-cache")) <br>mydata <- reactiveVal(data.frame(x = rnorm(400), y = rnorm(400))) <br> ui <- fluidPage( sidebarLayout( sidebarPanel( sliderInput("n", "Number of points", 50, 400, 100, step = 50), actionButton("newdata", "New data") ), mainPanel( plotOutput("plot") ) ) ) <br> server <- function(input, output, session) { observeEvent(input$newdata, { mydata(data.frame(x = rnorm(400), y = rnorm(400))) }) <br> 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()) } ) } <br>shinyApp(ui = ui, server = server)</code></pre> Once you launch the app, you'll see the following: <img class="size-full wp-image-13232" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3ce842ddef80cb74e45_d61ec8ae_1.webp" alt="Image 1 - Basic R Shiny app that uses renderCachedPlot" width="2244" height="1736" /> 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 <code>myapp-cache</code> directory: <img class="size-full wp-image-13234" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3d002cd257900c8d826_d1ca16ea_2.webp" alt="Image 2 - Contents of the myapp-cache directory" width="2064" height="1096" /> 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. <h2 id="bindcache">bindCache() - Easily Speed Up Dashboards with R Shiny Caching</h2> The <code>bindCache()</code> function adds caching to <code>reactive()</code> 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, <code>bindCache()</code> 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. <blockquote>Need to improve your Shiny dashboards? Explore <a href="https://shiny.tools/">Appsilon's open-source packages</a> to level up your performance and design.</blockquote> We'll demonstrate how <code>bindCache()</code> works by examining an example from <a href="https://rdrr.io/github/rstudio/shiny/man/bindCache.html" target="_blank" rel="nofollow noopener">rdrr.io</a>. 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: <pre><code class="language-r">library(shiny) shinyOptions(cache = cachem::cache_disk("./bind-cache")) <br>ui <- fluidPage( sliderInput("x", "x", 1, 10, 5), sliderInput("y", "y", 1, 10, 5), actionButton("go", "Go"), div("x * y: "), verbatimTextOutput("txt") ) <br> 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()) } <br>shinyApp(ui = ui, server = server)</code></pre> Here's what the corresponding Shiny app looks like: <img class="size-full wp-image-13236" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3d0b6953e1bff25f6ce_2c94e235_3.webp" alt="Image 3 - R Shiny app that uses bindCache()" width="2044" height="1962" /> 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 <code>bind-cache</code>. Here are the contents after a couple of calculations: <img class="size-full wp-image-13238" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3d146c250f02d9a02c8_36ca10b8_4.webp" alt="Image 4 - Contents of the bind-cache directory" width="2064" height="1096" /> Image 4 - Contents of the bind-cache directory Easy, right? Let's see how our last caching option for R Shiny works. <h2 id="memoise">memoise Package - Cache results of R Functions</h2> The <code>memoise</code> 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: <pre><code class="language-r">install.packages("memoise")</code></pre> 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 <code>cache_filesystem()</code> function for the task, but there are <a href="https://memoise.r-lib.org/reference/index.html" target="_blank" rel="nofollow noopener">others available</a>. Then, you'll want to write your R functions, followed by a call to <code>memoise()</code> 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: <pre><code class="language-r">library(shiny) library(memoise) cd <- cache_filesystem("./r-memoise") <br> multiply_nums <- function(x, y) { Sys.sleep(2) return (x * y) } mmn <- memoise(multiply_nums, cache = cd) <br>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 ) ) <br>server <- function(input, output, session) { r <- eventReactive(input$calculate, { mmn(x = input$x, y = input$y) }) output$txt <- renderText(r()) } <br>shinyApp(ui = ui, server = server)</code></pre> Once launched, the R Shiny app looks as follows: <img class="size-full wp-image-13240" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3d2f870288447682aa4_358581b2_5.webp" alt="Image 5 - R Shiny app that uses the memoise package" width="2044" height="2266" /> 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 <code>mmn()</code> function which calls <code>multiply_nums()</code> function with previously seen arguments, your results will be fetched from the cache. As expected, caching results are saved to the folder: <img class="size-full wp-image-13242" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d3d3cba09eb7527fb4b9_1466bdae_6.webp" alt="Image 6 - Contents of the r-memoise directory" width="2064" height="1096" /> Image 6 - Contents of the r-memoise directory And that's how you can use the <code>memoise</code> R package to cache results of R function, all wrapped in R Shiny. Let's wrap things up next. <hr /> <h2>Summary of R Shiny Caching</h2> 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 - <a href="https://twitter.com/appsilon" target="_blank" rel="nofollow noopener">@appsilon</a>. <blockquote>Ready to monitor user adoption in R Shiny? <a href="https://appsilon.com/monitoring-r-shiny-user-adoption/" target="_blank" rel="noopener">Shiny.stats, Shinylogs, and Google Analytics hold the answer</a>.</blockquote>