How to use "withProgress" for all calculations performed inside an ObserveEvent in Shiny?

Viewed 135

Hi and thanks for reading me I am making an application in Shiny that calculates a graph of a forecast using a neural network. The model (and graph) takes about 9 seconds to load, and I would like a progress bar to appear during that wait time, but I have not been able to get it to work correctly, since apparently it only appears at the moment in which the graphic. Do you know of any way that I can make the bar appear for all calculations?

The code (and data) is the following:

library(readxl)
library(dplyr)
library(shiny)
library(highcharter)
library(forecast)

datos <- data.frame(
  Servicio = sample(c("Servicio 1", "Servicio 2", "Servicio 3"), 162, replace = TRUE),
  Año_mes = seq(as.Date("1980-01-01"), as.Date("2020-05-31"), by = "quarter"),
  servs = rnorm(162, mean = 500)
) |> 
  setNames(c("Servicio", "Año_mes", "Número de Servicios"))

datos1 <- datos |> 
  group_by(Año_mes, Servicio) |> 
  summarise(total = sum(`Número de Servicios`)) 

datos_select <- datos |> 
  group_by(Servicio) |> 
  summarise(total = sum(datos$`Número de Servicios`))




datos_select <- datos_select$Servicio
datos_select

ui <- fluidPage(
  column(
    width = 6,
    selectInput("var",
                "Escoge un servicio a modelar", choices = datos_select
    ),
    numericInput("rezagosnoest", "Escoge un número de rezagos no estacionales:",1, min = -1000, max = 1000),
    numericInput("rezagossiest", "Escoge un número de rezagos estacionales:",1, min = -1000, max = 1000),
    numericInput("neuronas", "Escoge la cantidad de neuronas usadas para el cálculo:",1, min = 1, max = 1000),
    #numericInput("futuros", "Escoge el número de periodos (meses) a pronosticar:",1, min = 1, max = 1000),
    actionBttn(
      inputId = "modelar",
      label = "Generar pronóstico", 
      style = "bordered",
      color = "success",
      icon = icon("sliders")
    )),
  column(width = 6,
                  highchartOutput("grafico"))
)


server <- function(input, output){
  
  observeEvent(
    input$modelar,{
      filtrado <- datos1 |> 
        filter(Servicio == input$var) 
      temporal <- ts(filtrado$total, start = 2017, frequency = 12)
      set.seed(50)
      modelo <- nnetar(temporal, p=input$rezagossiest,P=input$rezagosnoest,
                       size=input$neuronas)
      nnetforecast <- forecast(modelo, h = 12, PI = T)
      output$grafico <- renderHighchart({
        withProgress(message = 'Calculando el modelo',
                     detail = 'Espera un momento...', value = 0, {
                       for (i in 1:15) {
                         incProgress(1/15)
                       }
                     })
        hchart(nnetforecast)
        
      })
    }
    
    
  )
  
}

shinyApp(ui, server)
1 Answers

I think you can't add a precise progressbar without twitching the used function to give you feedback on the actual progress or having another function to estimate the needed time depending on the user input.

If you don't have the time to dig on that, I would encourage you to use one or both of the following packages:

-https://dreamrs.github.io/shinybusy/

-https://github.com/daattali/shinycssloaders

Both provide a good service letting the user know that your app hasn't crush.

I provide the following example adding a conditional panel to hide the plot until action button is cliked.

Adding CSS loaders & conditional panel

library(shinycssloaders)
library(shiny)

ui <- fluidPage(
  
  column(
    width = 6,
    selectInput("var",
                "Escoge un servicio a modelar", choices = datos_select
    ),
    numericInput("rezagosnoest", "Escoge un número de rezagos no estacionales:",1, min = -1000, max = 1000),
    numericInput("rezagossiest", "Escoge un número de rezagos estacionales:",1, min = -1000, max = 1000),
    numericInput("neuronas", "Escoge la cantidad de neuronas usadas para el cálculo:",1, min = 1, max = 1000),
    #numericInput("futuros", "Escoge el número de periodos (meses) a pronosticar:",1, min = 1, max = 1000),
    actionBttn(
      inputId = "modelar",
      label = "Generar pronóstico", 
      style = "bordered",
      color = "success",
      icon = icon("sliders")
    )),
  column(width = 6,
         
         conditionalPanel("input.modelar > 0",
                          shinycssloaders::withSpinner(
                            highchartOutput("grafico")
                          ),
         )
  ) 
)

server <- function(input, output){
 
  observeEvent(
    input$modelar,{
      
      output$grafico <- renderHighchart({  
          filtrado <- datos1 |> 
            filter(Servicio == input$var) 
          temporal <- ts(filtrado$total, start = 2017, frequency = 12)
          set.seed(50)
          modelo <- nnetar(temporal, p=input$rezagossiest,P=input$rezagosnoest,
                           size=input$neuronas)
          nnetforecast <- forecast(modelo, h = 12, PI = T)
          
          hchart(nnetforecast)
      }) 
    }) 
}

shinyApp(ui, server)

This lead me into another issue, you are using an actionbutton as a submitbutton. The problem with this as you can see is that the input changes activate the reactivity that's why I added isolate():

Preventing reactivity

server <- function(input, output){
  
  observeEvent(
    input$modelar,{
      
      output$grafico <- renderHighchart({
        isolate({    
          filtrado <- datos1 |> 
            filter(Servicio == input$var) 
          temporal <- ts(filtrado$total, start = 2017, frequency = 12)
          set.seed(50)
          modelo <- nnetar(temporal, p=input$rezagossiest,P=input$rezagosnoest,
                           size=input$neuronas)
          nnetforecast <- forecast(modelo, h = 12, PI = T)
          
          hchart(nnetforecast)
        })    
      })  
    })
}

If you still want to add the progress bar I guess you can add it parallel to the rendering of the chart:

Final answer with parallel progresbar

library(readxl)
library(dplyr)
library(shiny)
library(highcharter)
library(forecast)
library(shinycssloaders)

datos <- data.frame(
  Servicio = sample(c("Servicio 1", "Servicio 2", "Servicio 3"), 162, replace = TRUE),
  Año_mes = seq(as.Date("1980-01-01"), as.Date("2020-05-31"), by = "quarter"),
  servs = rnorm(162, mean = 500)
) |> 
  setNames(c("Servicio", "Año_mes", "Número de Servicios"))

datos1 <- datos |> 
  group_by(Año_mes, Servicio) |> 
  summarise(total = sum(`Número de Servicios`)) 

datos_select <- datos |> 
  group_by(Servicio) |> 
  summarise(total = sum(datos$`Número de Servicios`))




datos_select <- datos_select$Servicio
datos_select

ui <- fluidPage(
  
  column(
    width = 6,
    selectInput("var",
                "Escoge un servicio a modelar", choices = datos_select
    ),
    numericInput("rezagosnoest", "Escoge un número de rezagos no estacionales:",1, min = -1000, max = 1000),
    numericInput("rezagossiest", "Escoge un número de rezagos estacionales:",1, min = -1000, max = 1000),
    numericInput("neuronas", "Escoge la cantidad de neuronas usadas para el cálculo:",1, min = 1, max = 1000),
    #numericInput("futuros", "Escoge el número de periodos (meses) a pronosticar:",1, min = 1, max = 1000),
    actionBttn(
      inputId = "modelar",
      label = "Generar pronóstico", 
      style = "bordered",
      color = "success",
      icon = icon("sliders")
    )),
  column(width = 6,
         
         conditionalPanel("input.modelar > 0",
                          shinycssloaders::withSpinner(
                            highchartOutput("grafico")
                          ),
         )
  )
  
)


server <- function(input, output){
  
  
  observeEvent(
    input$modelar,{
      output$grafico <- renderHighchart({
        isolate({    
          filtrado <- datos1 |> 
            filter(Servicio == input$var) 
          temporal <- ts(filtrado$total, start = 2017, frequency = 12)
          set.seed(50)
          modelo <- nnetar(temporal, p=input$rezagossiest,P=input$rezagosnoest,
                           size=input$neuronas)
          nnetforecast <- forecast(modelo, h = 12, PI = T)
          
          hchart(nnetforecast)
        })
        
      })
      
    })
  
  
  observeEvent(input$modelar,{
    withProgress(message = 'Calculando el modelo',
                 detail = 'Espera un momento...', value = 0, {
                   for (i in 1:15) {
                     Sys.sleep(1)
                     incProgress(i/15)
                   }
                 })
  })
  
  
}

shinyApp(ui, server)

Final recommendation

Get into the nnet and add some progress indicator (iterations or something) pull it out and use it in combination with the cssloader to cover the plotting time.

Related