Search code examples
rggplot2shinyautoplotfabletools

How to combine the line series in two plots rendered in autoplot into a single plot, sharing the same y-axis and with two different x-axes?


In the below code, I render two separate plots using the autoplot() function. The first plot (transPlot1) allows the user to transform the data series via slider input, whereas the 2nd plot shows the original untransformed series (transPlot2) and is completely static. I'd like to show the two data series in the same plot, with the first series shown on the primary x-axis (on the left, and its x-axis values vary depending on the slider input) and the 2nd series shown on the secondary x-axis (on the right, and it always remains fixed as this shows the original data before transformation). I'd like to place the two series on the same plot so the user can see the effect on the transformation on the shape of the data. This is the sort of thing that was easy to do in XLS but I have migrated to R.

Any recommendations for how to do this?

Code:

library(feasts)
library(fabletools)
library(ggplot2)
library(shiny)
library(tsibble)

DF <- data.frame(
  Month = c(1:12),
  StateX = c(59,77,45,42,32,26,27,21,19,22,24,10)
)
DF1 <- DF %>% as_tsibble(index = Month)

library(shiny)
ui <- fluidPage(
  sliderInput("lambda","Transformation lambda:",min=-2,max=2,value=0.5,step = 0.1),
  plotOutput("transPlot1"),
  plotOutput("transPlot2"),
)
server <- function(input, output) {
  
  output$transPlot1 <- renderPlot({
    DF1 %>%
      autoplot(box_cox(StateX, input$lambda)) +
      labs(y = "",
           title = latex2exp::TeX(paste0(
             "Transformed units reaching StateX",
             round(input$lambda,2))))
  })
  
  output$transPlot2 <- renderPlot({
    autoplot(DF1, StateX) +
      labs(y = "")
  })  
}
shinyApp(ui, server)

Solution

  • One option to achieve your desired result would be to add your untransformed series to the autoplot via a geom_line and to add a secondary scale. As usual when adding a secondary scale to a ggplot this requires some transformation of the data to be displayed on the secondary axis. To this end I added a reactive to do all the transformations, including the Box-Cox transformation and the transformation needed for the secondary scale. For the last part I use scales::rescale.

    library(feasts)
    #> Loading required package: fabletools
    library(fabletools)
    library(ggplot2)
    library(shiny)
    library(tsibble)
    library(dplyr)
    library(scales)
    
    DF <- data.frame(
      Month = c(1:12),
      StateX = c(59, 77, 45, 42, 32, 26, 27, 21, 19, 22, 24, 10)
    )
    DF1 <- DF %>% as_tsibble(index = Month)
    
    library(shiny)
    ui <- fluidPage(
      sliderInput("lambda", "Transformation lambda:", min = -2, max = 2, value = 0.5, step = 0.1),
      plotOutput("transPlot1")
    )
    server <- function(input, output) {
      DF1_trans <- reactive({
        DF1 %>%
          mutate(
            state_x_box = box_cox(StateX, input$lambda),
            state_x_raw = scales::rescale(StateX, to = range(state_x_box))
          )
      })
      
      output$transPlot1 <- renderPlot({
        to_range <- range(DF1_trans()$StateX)
    
        DF1_trans() %>%
          autoplot(state_x_box) +
          geom_line(data = DF1_trans(), aes(Month, state_x_raw, color = "Untransformed Series")) +
          scale_y_continuous(
            sec.axis = sec_axis(
              name = "Untransformed",
              trans = ~ scales::rescale(.x, to = to_range))) +
          labs(
            y = "",
            title = latex2exp::TeX(paste0(
              "Transformed units reaching StateX",
              round(input$lambda, 2)
            ))
          )
      })
    }
    shinyApp(ui, server)
    #> 
    #> Listening on http://127.0.0.1:3492