0
votes

I am having trouble when initializing a shiny app in R. I would like eventReactive to trigger from any of several events, which are chained by reactive expressions. The app mostly works as intended, but does not display upon initialization and instead requires user to select an actionButton before results are displayed. Why is this?

I read documentation for eventReactive, played with ignoreNULL and ignoreInit settings, and done many online searches.

Example below.

require(shiny)
require(ggplot2)

ui <- fluidPage(
  titlePanel("Car Weight"),
  br(),
  uiOutput(outputId = "cylinders"),
  sidebarLayout(
    mainPanel(
      # plotOutput(outputId = "trend"),
      # plotOutput(outputId = "hist"),
      tableOutput("table"),
      uiOutput(outputId = "dataFilter"),
      actionButton(inputId = "update1", label = "Apply Filters"),
      width = 9
    ),
    sidebarPanel(
      actionButton(inputId = "update2", label = "Apply Filters"),
      uiOutput(outputId = "modelFilter"),
      actionButton(inputId = "update3", label = "Apply Filters"),
      width = 3
    )
  )
)

server <- function(input, output) {
  # Read data.  Real code will pull from database.
  df <- mtcars
  df$model <- row.names(df)

  # Get cylinders
  output$cylinders <- renderUI(
    selectInput(
      inputId = "cyl",
      label = "Select Cylinders",
      choices = c("", as.character(unique(df$cyl)))
    )
  )

  # Subset data by cyl.
  df2 <-
    reactive(droplevels(df[df$cyl == input$cyl, ]))

  # Filter data.
  df3 <-
    eventReactive({
      ##############################################################
      # Help needed:
      # Why does this block not update upon change in 'input$cyl'?
      ##############################################################
      input$update1
      input$update2
      input$update3
      input$cyl
    },
    {
      req(input$modelFilter)
      modelFilterDf <-
        data.frame(model = input$modelFilter)
      df3a <-
        merge(df2(), modelFilterDf, by = "model")
      df3a[df3a$wt >= input$dataFilter[1] &
             df3a$wt <= input$dataFilter[2],]
    },
    ignoreNULL = FALSE,
    ignoreInit = FALSE)

  # Plot table.
  output$table <- renderTable(df3())

  # Filter by data value.
  output$dataFilter <-
    renderUI({
      req(df2()$wt[1])
      sliderInput(
        inputId = "dataFilter",
        label = "Filter by Weight (1000 lbs)",
        min = floor(min(df2()$wt, na.rm = TRUE)),
        max = ceiling(max(df2()$wt, na.rm = TRUE)),
        value = c(
          min(df2()$wt, na.rm = TRUE),
          max(df2()$wt, na.rm = TRUE)
        ),
        step = round(
          max(df2()$wt, na.rm = TRUE) - min(df2()$wt, na.rm = TRUE)
        ) / 100,
        round = round(log((
          max(df2()$wt, na.rm = TRUE) - min(df2()$wt, na.rm = TRUE)
        ) / 100))
      )
    })

  # Filter by lot / wafer.
  output$modelFilter <- renderUI({
    req(input$cyl)
    checkboxGroupInput(
      inputId = "modelFilter",
      label = "Filter by Model",
      choices = as.character(unique(df2()$model)),
      selected = as.character(unique(df2()$model))
    )
  })
}

# Run shiny.
shinyApp(ui = ui, server = server)
1
The eventReactive is executed twice during start-up, but stops at req(input$modelFilter).ismirsehregal
Thanks @ismirsehregal. The code will execute if I remove the req(input$modelFilter), but will return Error: 'by' must specify a uniquely valid column when executing merge(df2(), modelFilterDf, by = "model"), which is not the desired result. Do you have any suggestion to address this?robot

1 Answers

2
votes

I found a solution. Perhaps not the most elegant, but it works.

The problem was that input$modelFilter and input$modelFilter were one update behind df2. This did not matter when the user selected input$update, since df2 did not update, and only posed a problem during a newly created df2, since the filter would not match the data.

To resolve this, I added values <- reactiveValues(update = 0) which will increase by +1 every time df3 is created, and will reset back to 0 when a new df2 is created. If values$update > 0 then the data is filtered, otherwise, the unfiltered data is returned.

Possibly useful link: How can I set up triggers or execution order for eventReactive or ObserveEvent?

require(shiny)
require(ggplot2)

ui <- fluidPage(
  titlePanel("Car Weight"),
  br(),
  uiOutput(outputId = "cylinders"),
  sidebarLayout(
    mainPanel(
      tableOutput("table"),
      uiOutput(outputId = "dataFilter"),
      actionButton(inputId = "update1", label = "Apply Filters"),
      width = 9
    ),
    sidebarPanel(
      actionButton(inputId = "update2", label = "Apply Filters"),
      uiOutput(outputId = "modelFilter"),
      actionButton(inputId = "update3", label = "Apply Filters"),
      width = 3
    )
  )
)

server <- function(input, output) {
  # Read data.  Real code will pull from database.
  df <- mtcars
  df$model <- row.names(df)
  df <- df[order(df$model), c(12,1,2,3,4,5,6,7,8,9,10,11)]

  # Get cylinders
  output$cylinders <- renderUI({
    selectInput(
      inputId = "cyl",
      label = "Select Cylinders",
      choices = c("", as.character(unique(df$cyl)))
    )})

  # Check if data frame has been updated.
  values <- reactiveValues(update = 0)

  # Subset data by cyl.
  df2 <-
    reactive({
      values$update <- 0
      df2 <- droplevels(df[df$cyl == input$cyl,])})

  # Filter data.
  df3 <-
    eventReactive({
      input$update1
      input$update2
      input$update3
      df2()
    },
    {
      if (values$update > 0) {
        req(input$modelFilter)
        modelFilterDf <-
          data.frame(model = input$modelFilter)
        df3a <-
          merge(df2(), modelFilterDf, by = "model")
        df3a <- df3a[df3a$wt >= input$dataFilter[1] &
                       df3a$wt <= input$dataFilter[2], ]
      } else {
        df3a <- df2()
      }

      values$update <- values$update + 1
      df3a
    },
    ignoreNULL = FALSE,
    ignoreInit = TRUE)

  # Plot table.
  output$table <- renderTable(df3())

  # Filter by data value.
  output$dataFilter <-
    renderUI({
      req(df2()$wt[1])
      sliderInput(
        inputId = "dataFilter",
        label = "Filter by Weight (1000 lbs)",
        min = floor(min(df2()$wt, na.rm = TRUE)),
        max = ceiling(max(df2()$wt, na.rm = TRUE)),
        value = c(floor(min(df2()$wt, na.rm = TRUE)),
                  ceiling(max(df2()$wt, na.rm = TRUE))),
        step = round(max(df2()$wt, na.rm = TRUE) - min(df2()$wt, na.rm = TRUE)) / 100,
        round = round(log((
          max(df2()$wt, na.rm = TRUE) - min(df2()$wt, na.rm = TRUE)
        ) / 100))
      )
    })

  # Filter by lot / wafer.
  output$modelFilter <- renderUI({
    req(input$cyl)
    checkboxGroupInput(
      inputId = "modelFilter",
      label = "Filter by Model",
      choices = as.character(unique(df2()$model)),
      selected = as.character(unique(df2()$model))
    )
  })
}

# Run shiny.
shinyApp(ui = ui, server = server)