Caching in R / Shiny

Just wondering if there are tricks / ways that I could cache charts created using our brilliant app.

Background:

We do some computational intensive computing that ultimately leads to the plot. I already cache (using memoise) the calculations done globally in a brilliant state, but it takes 0.75 more seconds to complete the plot. I'm just wondering if we can reduce this time by removing the time it takes to render the image, and if there are blurry ways of doing it.

More details:

I use a grid to create a graph (in this case it is a heat map. Ideally, I would like the caching to be based on disks, since saving the graphs in memory will not be scaled.

Thanks! -Abhi

+10
r shiny shiny-server
source share
3 answers

Assuming you are using ggplot (which I would put with Shiny, this is a fair assumption).

  • Create an empty list to store your grob, say Plist .
  • When the user requests a graph, create a hash string based on the brilliant inputs
  • Check if graph is saved, e.g. hash %in% names(Plist)
  • If yes, open this chart.
  • If not, generate a graph, save the grob in the list, name the item a hash, for example Plist[hash] <- new_graph
+6
source share

Edit

Caching of images created using renderPlot()/plotOutput() has been supported since version 1.2.0.

The solution below behaves similarly to the following use of renderCachedPlot() .

 output$plot <- renderCachedPlot( expr = { histfaithful(bins = input$bins, col = input$col) }, cache = diskCache() ) 

renderCachedPlot() allows you to cache in memory and on disk with reasonable default values. Rules for generating hash keys can be configured and, by default, digest::digest() used for all reactive expressions that appear in expr .

The solution below demonstrates how a subset of these features (disk caching) can be implemented using a brilliant module. The main strategy is to use

  • digest::digest() to create cache keys based on arguments sent to the build function
  • do.call() to pass arguments to the plot function if the key created from digest() does not mean that the image is already cached
  • grDevices::png() to capture the image from the call to do.call() and add it to the cache
  • shiny::renderImage() to serve cache images

Original answer

Although both answers to this question are very good, I would like to add another one using brilliant modules . The next module accepts a graphical function and a reactive version of the arguments as input. In the end, do.call(plotfun, args()) used to create the plot.

 library(shiny) cachePlot <- function(input, output, session, plotfun, args, width = 480, height = 480, dir = tempdir(), prefix = "cachedPlot", deleteonexit = TRUE){ hash <- function(args) digest::digest(args) output$plot <- renderImage({ args <- args() if (!is.list(args)) args <- list(args) imgpath <- file.path(dir, paste0(prefix, "-", hash(args), ".png")) if(!file.exists(imgpath)){ png(imgpath, width = width, height = height) do.call(plotfun, args) dev.off() } list(src = imgpath) }, deleteFile = FALSE) if (deleteonexit) session$onSessionEnded(function(){ imgfiles <- list.files(dir, pattern = prefix, full.names = TRUE) file.remove(imgfiles) }) } cachePlotUI <- function(id){ ns <- NS(id) imageOutput(ns("plot")) } 

As we can see, the module deletes the created image files, if necessary, and makes it possible to use the user directory for caching if constant caching is necessary (as in my real use case).

As an example of use, I will use the hist(faithful[, 2]) example, just like Stedy.

 histfaithful <- function(bins, col){ message("calling histfaithful with args ", bins, " and ", col) x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = bins + 1) hist(x, breaks = bins, col = col, border = 'white') } shinyApp( ui = fluidPage( inputPanel( sliderInput("bins", "bins", 5, 30, 10, 1), selectInput("col", "color", c("blue", "red")) ), cachePlotUI("cachedPlot") ), server = function(input, output, session){ callModule( cachePlot, "cachedPlot", histfaithful, args = reactive(list(bins = input$bins, col = input$col)) ) } ) 
+5
source share

The answer from Ricardo Saporta is very good, and I used to solve a similar problem, but I also wanted to add a solution for the code.

For caching, I used digest::digest() , where I just filed a list of parameters for this particular graph of this function to create a hash string. Initially, I thought that I would need to extract the hash string from observe() , and then use stat if / else to determine whether to send it to renderImage() or renderPlot() based on whether the image was previously created. I worked a bit with this and then came across only renderImage() . This is not an ideal image replacement, but more than close enough for the purposes of this demonstration.

ui.R

 library(shiny) fluidPage( sidebarLayout( sidebarPanel( sliderInput("bins", "Number of bins:", min = 1, max = 50, value = 25), selectInput("plot_color", "Barplot color", c("green"="green", "blue"="blue")) ), mainPanel( plotOutput("distPlot", width='100%', height='480px') ) ) ) 

and server.R

 library(shiny) function(input, output) { base <- reactive({ fn <- digest::digest(c(input$bins, input$plot_color)) fn}) output$distPlot <- renderImage({ filename <- paste0(base(), ".png") if(filename %in% list.files()){ list(src=filename) } else { x <- faithful[, 2] bins <- seq(min(x), max(x), length.out = input$bins + 1) png(filename) hist(x, breaks = bins, col = input$plot_color, border = 'white') dev.off() list(src=filename) } }, deleteFile = FALSE) } 
+3
source share

All Articles