rshinyshiny-reactivity

will observeEvent() have better performance than isolate()?


I got confused about the usage of isolate() and observeEvent(). As the codes shown below, those two codes seem to do the same work—rendering the plot only after clicking the button. However, when I am using the isolate() function in my app deployed on shinyapps.io, my app got crashed because of too much memory usage. (I have simplified the graphing code here only for demonstration. The actual codes are much more complex.) I am wondering if the isolate() does anything with this bug. Will the replacement of the isolate() function by the observeEvent() help improve performance?

library(shiny)
library(stats)

runApp(list(
  ui = bootstrapPage(
    textInput(inputId = "text_in",
              label = "Type something:"),
    actionButton(inputId = "submit",
                 label = "submit"),
    plotOutput(outputId = "testpic")
  ),
  server = function(input, output) {
    output$testpic <- renderPlot({
      if (input$submit == FALSE) return()
      isolate({
        x  <- as.matrix(mtcars)
        rc <- rainbow(nrow(x), start = 0, end = .3)
        cc <- rainbow(ncol(x), start = 0, end = .3)
        heatmap(x, col = cm.colors(256), scale = "column",
                RowSideColors = rc, ColSideColors = cc, margins = c(5,10),
                xlab = input$text_in, ylab =  input$text_in,
                main = input$text_in)
      })
    })
  }
))
library(shiny)
library(stats)

runApp(list(
  ui = bootstrapPage(
    textInput(inputId = "text_in",
              label = "Type something:"),
    actionButton(inputId = "submit",
                 label = "submit"),
    plotOutput(outputId = "testpic")
  ),
  server = function(input, output) {
    vplot <- eventReactive(input$submit, {
      x  <- as.matrix(mtcars)
      rc <- rainbow(nrow(x), start = 0, end = .3)
      cc <- rainbow(ncol(x), start = 0, end = .3)
      heatmap(x, col = cm.colors(256), scale = "column",
              RowSideColors = rc, ColSideColors = cc, margins = c(5,10),
              xlab = input$text_in, ylab =  input$text_in,
              main = input$text_in)
    })
    
    observeEvent(input$submit, {
      output$testpic <- renderPlot({vplot()})
    })
  }
))

I have used profvis() to compare their performance difference. It turned out that maybe isolate() does better than observeEvent, which is quite shocking and goes against my previous knowledge.


Solution

  • Enclosing an output slot inside an observer is bad practice. I think that's why profvis reports the worst performance.

    I see three possibilities for the server:

    library(shiny)
    
    ui <- bootstrapPage(
      textInput(inputId = "text_in", label = "Type something:"),
      actionButton(inputId = "submit", label = "submit"),
      plotOutput(outputId = "testpic")
    )
    
    server1 <- function(input, output) {
      
      output$testpic <- renderPlot({
        req(input$submit)
        isolate({
          x  <- as.matrix(mtcars)
          rc <- rainbow(nrow(x), start = 0, end = .3)
          cc <- rainbow(ncol(x), start = 0, end = .3)
          heatmap(x, col = cm.colors(256), scale = "column",
                  RowSideColors = rc, ColSideColors = cc, margins = c(5,10),
                  xlab = input$text_in, ylab =  input$text_in,
                  main = input$text_in)
        })
      })
      
    }
    
    server2 <- function(input, output) {
      
      Heatmap <- eventReactive(input$submit, {
        x  <- as.matrix(mtcars)
        rc <- rainbow(nrow(x), start = 0, end = .3)
        cc <- rainbow(ncol(x), start = 0, end = .3)
        heatmap(x, col = cm.colors(256), scale = "column",
                RowSideColors = rc, ColSideColors = cc, margins = c(5,10),
                xlab = input$text_in, ylab =  input$text_in,
                main = input$text_in)
      })
      
      output$testpic <- renderPlot({
        Heatmap()
      })
      
    }
    
    server3 <- function(input, output) {
      
      output$testpic <- renderPlot({
        x  <- as.matrix(mtcars)
        rc <- rainbow(nrow(x), start = 0, end = .3)
        cc <- rainbow(ncol(x), start = 0, end = .3)
        heatmap(x, col = cm.colors(256), scale = "column",
                RowSideColors = rc, ColSideColors = cc, margins = c(5,10),
                xlab = input$text_in, ylab =  input$text_in,
                main = input$text_in)
      }) |> bindEvent(input$submit)
      
    }
    

    Then you can run:

    profvis::profvis({
      app <- shinyApp(ui, server1)
      runApp(app)
    })
    profvis::profvis({
      app <- shinyApp(ui, server2)
      runApp(app)
    })
    profvis::profvis({
      app <- shinyApp(ui, server3)
      runApp(app)
    })
    

    I tried and the conclusion is that server3 is the best in terms of memory, and server1, using isolate, is the worst.