rggplot2shiny

How to have Shiny app render multiple plots


I'm building a small shiny app to demo some causal inference concepts and I want to split all the independent variables from a linear regression formula and show them in multiple plots.

Strangely, when I look through the list of independent variables, shiny renders the last of them twice rather than rendering the first and then the second.

Here's the UI part of my code:

  ui <- fluidPage(
    textInput("regression", "Regression formula", ""),
    verbatimTextOutput("summary"),
    
    plotOutput("plot1", width=fig.width, height=fig.height),
    plotOutput("plot2", width=fig.width, height=fig.height),
    plotOutput("plot3", width=fig.width, height=fig.height),
  )

and here's where I split the formula into multiple parts and try to render all the independent variables:

server <- function(input, output) {
    
    parse_lm <- reactive({
      
      formula <- input$regression
      
      if(formula == "" || grepl("~", formula) == FALSE) {
        return()
      }
      first_split <- unlist(strsplit(formula, "[~]"))
      dependent <- str_trim(first_split[1])
      independent <- unlist(lapply(strsplit(first_split[2], "[+]"), str_trim))
      
      for( i in 1:length(independent))
      {
        whichplot <- paste("plot", toString(i), sep="")
        
        print(whichplot) # this prints "plot1" and "plot2"
        print(independent[i]) # this prints "vs" and "am"
        
        output[[whichplot]] <- renderPlot({
          ggplot(mtcars, aes(x = .data[[dependent]])) +
            geom_histogram(aes(color = as.factor(.data[[independent[i]]]), fill = as.factor(.data[[independent[i]]])), 
                           position = "identity", bins = 30, alpha = 0.2) +
            scale_color_manual(values = c("#00AFBB", "#E7B800")) +
            scale_fill_manual(values = c("#00AFBB", "#E7B800"))
        })
      }
   })

    observeEvent(input$regression, { 
      parse_lm()
    })
}

When I pass "mpg ~ vs + am" as the formula, what should happen is that I get a hist of both 'vs' and 'am' but instead I just get 'am' twice. I suspect that shiny is re-rendering the plot but I can't figure out why.


Solution

  • You have an issue with lazy evaluation. The since you use a for loop, you are mutating the value of i. That value is remains unevaluated till the polts are drawn and by that time the value if i has changed. It would be better to us something like lapply to create a closure on the value of i, that way it won't change.

    lapply(1:length(independent), function(i) {
          whichplot <- paste("plot", toString(i), sep="")
          
          print(whichplot) # this prints "plot1" and "plot2"
          print(independent[i]) # this prints "vs" and "am"
          
          output[[whichplot]] <- renderPlot({
            ggplot(mtcars, aes(x = .data[[dependent]])) +
              geom_histogram(aes(color = as.factor(.data[[independent[i]]]), fill = as.factor(.data[[independent[i]]])), 
                             position = "identity", bins = 30, alpha = 0.2) +
              scale_color_manual(values = c("#00AFBB", "#E7B800")) +
              scale_fill_manual(values = c("#00AFBB", "#E7B800"))
          })
        })