At Linear Squared, we build interactive applications to communicate findings and monitor business KPIs via complex algorithms. When designing new product enhancements there’s often a sudden need to demonstrate the functionality of these new components. For this purpose, to prototype, Linear Squared uses R shiny as it can be used to build interactive applications easily.

There are many free resources to learn R Shiny on your own such as the following:

https://shiny.rstudio.com/articles/basics.html

https://shiny.rstudio.com/tutorial/

When prototyping RShiny solutions, I came across a few challenges for which there were little to no good resources with guidance – especially for newer versions of RShiny. The goal of this blog post is to help RShiny users in tackling 3 of these problems, which I believe are common. At the end of this blog post, you will have the answers for the following:

  1. How to create an editable table where the changes are reflected in the plot
  2. How to call a python script from R shiny
  3. How to give an indication when R shiny is processing

In order to cover all of the mentioned above, I’m going to build a small example app.  

Sales demand forecasting is a common task in the industry. Organizations often do resource planning using sales forecasts as inputs. Hence, I thought of using this scenario to create our example app. The app will compute sales forecast and then the sales forecast will be used as an input to an KPI optimizer algorithm written in Python, which will give you an optimizer output table based on the given demand. You can input your favorite forecasting algorithm and optimization problem and solution where appropriate.

Now let’s get started!

Figure 1: The UI Design

The layout of the app is shown in figure 1.

There are 2 tabs where one will display the demand forecast and the other will display the generated optimizer output.

## Import the libraries
library(shiny)
library(DT)
library(shinythemes)
library(forecast)

ui <- fluidPage(
  theme = shinytheme("cerulean"),
  titlePanel(title = div("LinearSquared R Shiny Example")),
  
  sidebarLayout(
    sidebarPanel(
      fileInput("file1", "Upload Sales CSV File", multiple = FALSE, accept = c("text/csv","text/comma-separated-values,text/plain",".csv")),
      
      actionButton("action", "Generate")
      
    ),
    
 mainPanel(
      
      tabsetPanel(
        
        tabPanel("Demand Forecast",                      
                 fluidRow(id="table1",style = "font-size:12px;width:100% !important", dataTableOutput("contents")),
                 
                 fluidRow(id="chart",style = "margin-top: 40px;width:100% !important", plotOutput("lineChart"))
                 
        ),

        tabPanel("Optimizer Output",                 
                 dataTableOutput("tableOpt"),           
                 conditionalPanel("$('#tableOpt').hasClass('recalculating')", 
                                  tags$div('This may take a while... ')
                 )
        )      
      )
    )   
  )      
)

######################################################################

server <- function(input, output,session) {  
  
  ################### DEMAND FORECAST ###################
  
  valuesDem = reactiveValues(df_dem = NULL) # initialization
  
  observeEvent(input$file1, { # compute forecasts after observing the input file
    h = 6 # 6 steps ahead forecast
    histDat <-  read.csv(input$file1$datapath) # user input file data
    
    # Fit an ARIMA model and get forecast
    tsModel <- Arima(histDat$Demand, order = c(1,0,1), seasonal = list(order=c(1,0,0), period=12))
    Demand <- round(as.data.frame(forecast(tsModel, h))$'Point Forecast', 0)

    lastDate <- as.Date(as.character(histDat$Date[nrow(histDat)]), format= "%m/%d/%Y")
    Date <- seq(as.Date(lastDate), by = "1 month", length.out = h+1)[2:(h+1)]
    valuesDem$df_dem <- data.frame(Date, Demand)
    save_df <- valuesDem$df_dem 
    colnames(save_df) <- c("date", "demand")
    
    # Write CSV to the working directory
    write.csv(save_df, 'Demand_Forecast.csv')
    
  })
  
 
 output$contents <- renderDataTable({ # show the demand forecast table

    datFcts <- data.frame(valuesDem$df_dem)
    datFcts
  },height = 500, editable = TRUE)
  

  output$lineChart <- renderPlot({ # show the plot
    print("begin plotting........")
    req(input$file1)

    dat <- read.csv(input$file1$datapath)
    year <- as.Date(as.character(dat$Date), format= "%m/%d/%Y")
    sales <- dat$Demand
    
    demandFcts <- valuesDem$df_dem$Demand
    yearFcts <- valuesDem$df_dem$Date
    
    seriesDate <- c(year,yearFcts)
    seriesDem <- c(sales,demandFcts)
    
    chartTitle = "Product X"
    plot(seriesDem~as.Date(seriesDate),type="o",
         xlab="Date",ylab="Sale Value", col="#CCCCCC",lwd=4, cex.lab=1, main=paste("Sales vs Date for ", chartTitle))
    
    lines(seriesDate[(length(year)+1):(length(seriesDate))], demandFcts, type="o", col = "#999999", lwd=4, cex.lab=1)

  },height = 400)
  
  ################### OPTIMIZER OUTPUT ###################
  
  valuesOpt <- reactiveValues(df_data_opt = NULL) # initialize
  
  observeEvent(input$action, { 
    optTable <- read.csv("Optimizer_Table.csv", header = TRUE)
    optTable <- optTable[c("Optimizer_Output")]
    file.remove('Optimizer_Table.csv')
    valuesOpt$df_data_opt <- optTable
  })
  
  output$tableOpt <- renderDataTable({ # display the plot
    req(input$action)
    valuesOpt$df_data_opt
  })
    
}

shinyApp(ui = ui, server = server)

Figure 2: Data table and the plot after uploading “Sales Data.csv”

Case 1: How to create an editable table where the changes are reflected in the plot

Inside the server function, an editable data table has to be created showing the demand forecast. Here, I have used a univariate ARIMA to get 6 steps ahead forecasts using the sales data input file.

As shown in Figure 2, there’s a table with demand forecasts and a plot that shows the historical demand (light grey) and forecast demand (dark grey) With our current code, the plot would not reflect the changes if we update the data values in the table. In the next step, we give an option to the user to change the forecast values. To do that, you have to use cell edit function as below.

observeEvent(input$contents_cell_edit, {
    info = input$contents_cell_edit

    i = info$row
    j = info$col
    v = info$value
 
    valuesDem$df_dem[i, j] <- isolate(coerceValue(v, valuesDem$df_dem[i, j]))

    save_new_df <- data.frame(valuesDem$df_dem$Date,valuesDem$df_dem$Demand)
    colnames(save_new_df) <- c("date", "demand")
    write.csv(save_new_df, 'Demand_Forecast.csv')
  })

Remember that when you use cell edit, you have to give the same name you gave inside the ‘dataTableOutput’ inside the ui fnction. I have given the name “contents”, therefore the cell edit object can be called as contents_cell_edit.

Now, let’s change the value on 2018-05-01 to 18000 and see if the change is reflected on the plot. Then I’m going to write it to a csv file where the optimizer algorithm will read it later.

Figure 3: Change made on the data table has been reflected on the plot

Case 2: How to call a python file from R shiny app

This app will use the algorithm built for an optimizer written in Python language and output a table given the demand forecasts.

First, I have saved the required python files in the R working directory including the main.py. Now using the system command, I can call the python script as follows and the demand forecasts will be read from the csv inside the algorithm.

observeEvent(input$action, {
    system(paste('python main.py'), intern = TRUE, wait=TRUE) 
  })

So, whenever the generate button is clicked, observeEvent function will observe the action and will run the python file.

In the server part, we need to include one more thing to the previous observeEvent function as follows,

observeEvent(input$action, {

    system(paste('python main.py'), intern = TRUE, wait=TRUE)
    
    while (!file.exists('Optimizer_Table.csv')) {
      Sys.sleep(1)
    }
  })

It is important to use sys.sleep() here. Otherwise, R will throw a “file not found” error

After the algorithm is run, it will write a csv and then R would read it and display the Optimizer Table as shown in Figure 4.

Figure 5: Generated Optimizer table from algorithm

Case 3: How to give an indication when R shiny is processing

The Inventory Optimizer algorithm would take several minutes to run as it has some complex calculations. When the running is completed, it will write a csv file at the end with the optimizer output. The R shiny app will read it and display under the “Optimizer Output” tab. So until we get the output table, it is better to display something that would indicate that the algorithm is still processing.

In the UI, inside the Optimizer Output tab, we can use a conditional panel as following.

        tabPanel("Optimizer Output",
                 
                 dataTableOutput("tableOpt"),
conditionalPanel("$('#tableOpt').hasClass('recalculating')", 
              tags$div('This may take a while... ')
                 )
Figure : UI is blurred and displaying a message indicating R Shiny is in process

Now finally, if I generate another table with the changed demand value, the Optimizer Table will give a different output as shown in Figure 6.

Figure 6: Optimizer table for the changed demand forecast

Your Details

Let us know how to get back to you.


How can we help?

Feel free to ask a question or simply leave a comment.


Contact Us

Contact us about anything related to our company solutions or services.
We'll do our best to get back to you as soon as possible.


Contact Us

Contact us about anything related to our company solutions or services.
We'll do our best to get back to you as soon as possible.