Wrangling Data for Use in Shiny App

Hello,

I am hoping you can help with this question.

Background/Use Case: I am trying develop a shiny app so that management can quickly visualize forecast vs actual sales of various products within a company. The company has multiple products. Here is the slightly tricky part: although each product is attached to a single column of data for actual sales; the data for forecast sales may be a single column or multiple columns.

Key Question: Based on a single selection from selectInput in ui; I want render either 1 or 2 plots based on type of data that is associated with that choice.

Here is simple example, hopefully this serves the purpose of a reprex.

# raw data
df <- tibble(
  date = as.Date(c("2018-03-01", "2018-06-01", "2018-09-01", "2018-12-01")),
  ProductA_Actual = c(10, 12, 9, 14),
  ProductA_Forecast1 = c(9, 11, 10, 15),
  ProductB_Actual = c(20, 22, 27, 24),
  ProductB_Forecast1 = c(19, 21, 28, 23),
  ProductB_Forecast2 = c(22, 18, 28, 30)
)
# Converted to 'long' format 
df_long <- df %>%
  gather(Product_Type, Sales, -date) %>% 
  separate(Product_Type, c("Product", "Data_Type"), "_")
df_long

Shiny: Here is what I have written so far.

library(tidyverse)
library(shiny)
# UI
ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("Product", "Select Product", choices=as.character(unique(df_long$Product)), selected="ProductA")
    ),
    mainPanel(
      h3("Sales: Forecasts vs Actual"),
      plotOutput("plot_1"),
      plotOutput("plot_2")
  )
 )
)

## server
server <- function(input, output) {
  output$plot_1 <- renderPlot({
    df_long %>% 
      filter(Product == input$Product) %>% 
      filter(Data_Type %in% c("Actual" , "Forecast1")) %>% 
      ggplot(aes(x=date)) +
      geom_line(aes(y=Sales, color=Data_Type),size=1) +
      geom_hline(yintercept = 0) +
      scale_x_date(breaks=unique(df_long$date), date_labels = "%b-%y") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position="right", legend.title = element_blank()) +
      labs(y="Sales Percentage Change Y/Y", x="Quarter") +
      ggtitle(label="Chart 1: Actual vs Forecast 1")

   })
  output$plot_2 <- renderPlot({
    df_long %>% 
      filter(Product == input$Product) %>% 
      filter(Data_Type %in% c("Actual" , "Forecast2")) %>% 
      ggplot(aes(x=date)) +
      geom_line(aes(y=Sales, color=Data_Type),size=1) +
      geom_hline(yintercept = 0) +
      scale_x_date(breaks=unique(df_long$date), date_labels = "%b-%y") +
      theme_bw() +
      theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position="right", legend.title = element_blank()) +
      labs(y="Sales Percentage Change Y/Y", x="Quarter") +
      ggtitle(label="Chart 2: Actual vs Forecast 2")

  })
}

shinyApp(ui=ui, server=server)

When ProductB is selected, the charts are displayed in the way that I want:

However, when ProductA is selected, I end up with an incomplete chart at the bottom. (ProductA only has one forecast, ProductB has two forecasts)

How can I re-write this so that a single selection of product will lead to either 1 or 2 (or more) plots based on the number of forecasts associated with that product? I am assuming that some combination of data wrangling/reshaping and some of the more advanced features in shiny (conditional panel? reactive?) will get me there, but I just cant seem to figure this out.

Thanks in advance!!

My suggestion is to put both forecasts on the same plot as that makes it easier to compare the forecasts with each other. In the code below I used facet_wrap just to save repeating the code for each product. You may well want individual plots for each product.

library(tibble)
library(tidyr)
library(ggplot2)
df <- tibble(
  date = as.Date(c("2018-03-01", "2018-06-01", "2018-09-01", "2018-12-01")),
  ProductA_Actual = c(10, 12, 9, 14),
  ProductA_Forecast1 = c(9, 11, 10, 15),
  ProductB_Actual = c(20, 22, 27, 24),
  ProductB_Forecast1 = c(19, 21, 28, 23),
  ProductB_Forecast2 = c(22, 18, 28, 30)
)
df_long <- df %>%
  gather(Product_Type, Sales, -date) %>% 
  separate(Product_Type, c("Product", "Data_Type"), "_")
ggplot(df_long, aes(x=date)) +
  geom_line(aes(y=Sales, color=Data_Type, group = Data_Type),size=1) +
  geom_hline(yintercept = 0) +
  scale_x_date(breaks=unique(df_long$date), date_labels = "%b-%y") +
  theme_bw() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position="right", legend.title = element_blank()) +
  labs(y="Sales Percentage Change Y/Y", x="Quarter") +
  ggtitle(label="Chart 1: Actual vs Forecast") + facet_wrap(~Product, nrow = 2)

Created on 2019-08-06 by the reprex package (v0.2.1)

1 Like

Thank you! Had not thought about facet_wrap for some reason. I think this should fix the issue.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.