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

Reading time:
time
min
By:
Dario Radečić
June 29, 2022

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 &lt;- reactiveVal(data.frame(x = rnorm(400), y = rnorm(400))) <br> ui &lt;- fluidPage(    sidebarLayout(    sidebarPanel(        sliderInput("n", "Number of points", 50, 400, 100, step = 50),        actionButton("newdata", "New data")    ),    mainPanel(        plotOutput("plot")    )    ) ) <br> server &lt;- function(input, output, session) {    observeEvent(input$newdata, {    mydata(data.frame(x = rnorm(400), y = rnorm(400)))    }) <br>    output$plot &lt;- renderCachedPlot({        Sys.sleep(2)        d &lt;- mydata()        seqn &lt;- 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 &lt;- fluidPage(    sliderInput("x", "x", 1, 10, 5),    sliderInput("y", "y", 1, 10, 5),    actionButton("go", "Go"),    div("x * y: "),    verbatimTextOutput("txt") ) <br> server &lt;- function(input, output, session) {    r &lt;- reactive({        message("Doing expensive computation...")        Sys.sleep(2)        input$x * input$y    }) %&gt;%        bindCache(input$x, input$y) %&gt;%        bindEvent(input$go)        output$txt &lt;- 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 &lt;- cache_filesystem("./r-memoise") <br> multiply_nums &lt;- function(x, y) {    Sys.sleep(2)    return (x * y) } mmn &lt;- memoise(multiply_nums, cache = cd) <br>ui &lt;- 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 &lt;- function(input, output, session) {    r &lt;- eventReactive(input$calculate, {        mmn(x = input$x, y = input$y)    })    output$txt &lt;- 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>

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
r
shiny
tutorials