0

I tried to add a filter to my data analysis. The filter (inputF2) is an item in a category (xInput) chosen by the user.

then I want filter out the data to do summarize analysis and plot out the mean. However, once I wrote the if statement, the program won't run.

library(datasets)
library(shiny)
library(dplyr)
library(ggplot2)
library(DT)
library(crosstalk)

data("iris")

# Define UI for application that draws a histogram
ui <- fluidPage(

  # Application title
  titlePanel("Analyze Iris table"),

  # Sidebar with a dropdown menu selection input for key measurecomponent
  sidebarLayout(
    sidebarPanel(
      selectInput("yInput", "Measuring element: ", 
                  colnames(iris), selected = colnames(iris)[2]), 
      selectInput('xInput', 'Grouper: ', 
                  colnames(iris), selected = colnames(iris)[5])
    ),

    # Show a plot of the generated distribution
    mainPanel(
      uiOutput('filter'),
      plotOutput("barPlot"),
      DTOutput('table1')
      )))
server <- function(input, output) {

  output$filter = renderUI({

    selectInput('inputF2', 'Filter Item: ', 
                c('Null', unique(iris %>% select(input$xInput))))
  })

  if(input$inputF2 != 'Null') {
    iris_sub = reactive({

      iris %>% filter_at(input$xInput == input$inputF2)

    })
  } else{ iris_sub = iris}

  by_xInput <- reactive({

    iris_sub %>% 
      group_by_at(input$xInput) %>% 
      summarize(n = n(), mean_y = mean(!! rlang::sym(input$yInput)))

  })

  output$barPlot <- renderPlot({

    # as the input is a string, use `aes_string`
    ggplot(data = by_xInput(), aes_string(x = input$xInput, y = "mean_y")) + 
      geom_bar(stat = 'identity')

  })

  output$table1 = renderDT(
    datatable(by_xInput())
    )
}

shinyApp(ui = ui, server = server)

This is the error message I got:

Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)

Yitong Li
  • 57
  • 6
  • I think you may need `%>% filter_at(vars(input$xInput), any_vars(. == input$inputF2))` – akrun Sep 16 '19 at 17:59
  • 1
    Also, I find that you are using `=` instead of `==` in `if` condions – akrun Sep 16 '19 at 18:01
  • Thanks @akrun! But I still got the same error with the reactive environment.... – Yitong Li Sep 16 '19 at 18:10
  • @akrun, yes... that == mistake in the if statement is a typo. And I don't know why the code were split into all these parts :((( sorry if it is hard to read. – Yitong Li Sep 16 '19 at 18:12
  • Possible duplicate of [Shiny Tutorial Error in R](https://stackoverflow.com/questions/17002160/shiny-tutorial-error-in-r) – A. Suliman Sep 16 '19 at 19:42
  • @akrun, I figured out half of it based on your suggestion. Turns out I need to modify the input in if statement too. So it would be if (vars(input$inputF2) == "Null') { iris_sub = reactive ({ iris %>% filter_at (vars(input$xInput), any_vars(. == input$inputF2). But this somehow doesn't apply the if selection well for me. Do you have any ideas of how? – Yitong Li Sep 16 '19 at 19:43
  • Where you have `input$inputF2 != 'Null'` do you actually have a possible value of that input that is the string `"Null"`? Or are you trying to check against an actual NULL object, which you would do with `is.null`? – camille Sep 16 '19 at 19:49
  • Hi @camille, yes I added "Null" into the list of inputF2, which is just one line above. I have played around with this this if statement a few time, but the best I got is the filtered plot with a specific non-'Null' input from inputF2. I can't generate the non-filtered plot no matter how I try.... – Yitong Li Sep 16 '19 at 21:29

1 Answers1

1

The reason you were getting the active reactive content error was because of this chunk

if(input$inputF2 != 'Null') {
    iris_sub = reactive({

      iris %>% filter_at(input$xInput == input$inputF2)

    })
  } else{ iris_sub = iris}

Here you are evaluating input$inputF2 but that can change with user selection, so the test needs to be inside a reactive().

Another good practice is to wrap variables like inputF2 in req, to ensure they will have a value before being evaluated. This is because you are rendering the widget for filter on the server side, and initially it will not have a value.

Note also, that the filtering condition filter(input$xInput == input$inputF2) would fail, because filter expects an unquoted variable name in the left hand side of that expression (but input$xInput is a character). You can convert input$xInput to a name with as.name() and then use bang-bang inside filter to evaluate it: filter(!!as.name(input$xInput) == input$inputF2)

After this changes, the filtering chunk becomes:

iris_sub <- reactive({
    x_in <- as.name(input$xInput)
    if (req(input$inputF2) != 'Null') {
      iris_sub <- iris %>% filter(!!x_in == input$inputF2)
    } else{
      iris_sub <- iris
    }
    return(iris_sub)
  })

Finally, it seems like your app allowed the user to choose the same variable as measuring element and as the grouper. Not sure this is a good idea, as it might throw errors because you can't modify a grouping variable. One way to control this is to use validate inside the reactive that does the summarising and produce a meaningful error message for the user:

validate(
      need(expr = input$xInput != input$yInput,
           message = "Can't summarise by group when 'grouper' is the same as 'measuring element'"))

Here is the whole app with these modifications.

library(datasets)
library(shiny)
library(dplyr)
library(ggplot2)
library(DT)
library(crosstalk)

data("iris")

# Define UI for application that draws a histogram
ui <- fluidPage(

  # Application title
  titlePanel("Analyze Iris table"),

  # Sidebar with a dropdown menu selection input for key measurecomponent
  sidebarLayout(
    sidebarPanel(
      selectInput("yInput", "Measuring element: ", 
                  colnames(iris), selected = colnames(iris)[2]), 
      selectInput('xInput', 'Grouper: ', 
                  colnames(iris), selected = colnames(iris)[5])
    ),

    # Show a plot of the generated distribution
    mainPanel(
      uiOutput('filter'),
      plotOutput("barPlot"),
      DTOutput('table1')
    )))

server <- function(input, output) {

  output$filter = renderUI({
    selectInput('inputF2',
                'Filter Item: ',
                c('Null', iris %>% select(input$xInput) %>% unique()))
  })

  iris_sub <- reactive({
    x_in <- as.name(input$xInput)
    if (req(input$inputF2) != 'Null') {
      iris_sub <- iris %>% filter(!!x_in == input$inputF2)
    } else{
      iris_sub <- iris
    }
    return(iris_sub)
  })

  by_xInput <- reactive({
    validate(
      need(expr = input$xInput != input$yInput,
           message = "Can't summarise by group when 'grouper' is the same as 'measuring element'"))

    iris_sub() %>%
      group_by_at(input$xInput) %>%
      add_tally() %>%
      summarize_at(.vars = vars(input$yInput),
                   .funs = list("mean_y" = mean))

  })

  output$barPlot <- renderPlot({

    # as the input is a string, use `aes_string`
    ggplot(data = by_xInput(), aes_string(x = input$xInput, y = "mean_y")) + 
      geom_bar(stat = 'identity')

  })

  output$table1 = renderDT(
    datatable(by_xInput())
  )
}

shinyApp(ui = ui, server = server)
teofil
  • 2,344
  • 1
  • 8
  • 17
  • Thank you sooo much for fixing this problem for me and explaining everything crystal clear! I have learned a lot from your response :) – Yitong Li Sep 18 '19 at 02:54