0
votes

I am in the process of learning Shiny and developing a simple app. The start of the program will allow a user to import a CSV file and then apply a filter variable(s) if needed. They will only be able to use factors as filter variables at this stage. I apply the filters on an iterative basis. So, one can apply a filter based on a factor level and then apply another factor level and so on until completed.

The best application I could find of being able to subset a reactive data frame was to apply the data frame as a reactive value. This seems to work, but I am having a couple issues that I can't figure out how to resolve.

1) Given the filtering is an iterative process, I would like to keep track and print out each variable and level applied during the filtering process. The best way I could figure out was creating a global variable (<<-) and using renderText to print out the contents after hitting the apply filter button. The issue is renderText just flashes on the screen and quickly disappears. I included a print to console statement that verifies the text is being saved correctly. I believe this is happening from the filter being applied to the reactive data frame and the updating process, but I can't figure out how to stop the text from disappearing on the screen?

2) When I try to save out the reactive data frame at the end of the shiny code, I get the following error "Warning: Error in $: $ operator is invalid for atomic vectors". I tried a couple things, but don't really understand what is going on here because the object "file$dfSource" is not like a normal reactive data frame dfSource()?

The shiny app below uses iris data so its easier to use/test. I don't know if applying the data frame to a reactive value is the best way to program this or if there is an easier way to do all this - just trying to learn best approach here.

library(shiny)
allfilters <- c()

ui <- (fluidPage(

# Application title
titlePanel("Filter Data"),

# Input Forms
sidebarLayout(
sidebarPanel(
    h3("Data"),
        checkboxInput("selectFilter", label = "Apply Filter Variable", value = FALSE),
        uiOutput("selectFilterVar"),
    uiOutput("selectFilterGroup"),
    helpText("Apply filter to data"),
    uiOutput("selectFilterButton"),
    helpText("Reset data to total"),
    uiOutput("selectResetButton"),   
    h3("Download Data"),    
        helpText("Download Data"),
        downloadButton("downloadData", "Download File")
    ),

# Output Forms
mainPanel(
  tabsetPanel(
     tabPanel("Summary",
     h2("Output Summary"),
     textOutput("ncases"),
     textOutput("selectedfilters")))
  )
  )
))

server <- (function(input, output, session) {

data <- iris
file <- reactiveValues(dfSource = data)

## Select Filter Variable
output$selectFilterVar <- renderUI({
       req(file$dfSource)
       if (input$selectFilter){
       selectInput("filterVar", "Select Filter Variable", multiple = FALSE, choices = sort(names(file$dfSource[, sapply(file$dfSource, is.factor), drop = FALSE])))
}         
})         

# Select Filter Group(s)
output$selectFilterGroup <- renderUI({
        req(file$dfSource)
        req(input$filterVar)
        if (input$selectFilter){
        selectInput("filterGroup", "Select Filter Group", multiple = TRUE, choices = sort(unique(file$dfSource[,input$filterVar])))
}
})

# Apply Filter Button
output$selectFilterButton <- renderUI({
         req(file$dfSource)
         if (input$selectFilter) {
          actionButton("filterButton", "Apply Filter")
}
})

# Apply filter group to data
    observeEvent(input$filterButton, {
    temp <- file$dfSource[(file$dfSource[,input$filterVar] %in% c(input$filterGroup)),]
    file$dfSource <- temp
})

# Reset Total Sample Button
output$selectResetButton <- renderUI({
         req(file$dfSource)
         if (input$selectFilter) {
          actionButton("resetButton", "Reset Total")
}
})

# Reset data to total sample
    observeEvent(input$resetButton, {
    file$dfSource <- data
    updateCheckboxInput(session, "selectFilter", value = FALSE)
    allfilters <- NULL
})

## Summary number of cases
output$ncases <- renderText({
  req(file$dfSource)
  mainTitle <- paste("Number of cases =" , nrow(file$dfSource))
  return(mainTitle)  
})

## Capture selected filter variables in global object
testfilter <- eventReactive(input$filterButton, {
     appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
      if (is.null(allfilters)) {
        allfilters <<- paste("Selected Filters:", appliedfilter)
        } else {
        allfilters <<- paste(allfilters, "&", appliedfilter)
   }
   return(allfilters)
   })

# Print out filter variables in global object
output$selectedfilters <- renderText({
    filteroutput <- testfilter()
    print(filteroutput)
    return(filteroutput)
})

## Save out case data file
output$downloadData <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
    write.csv(file$dfSource, file)
    }
  )

})

shinyApp(ui, server)
1

1 Answers

0
votes

1) Storing it a global variable is probably not a good idea (scope in shiny is already complicated enough!). You already have a reactiveValues object, why not use that?

This alone, however, is not enough; the problem seems to be the eventReactive - I'm not quite sure why.

This works:

# this replaces the testfilter eventReactive
observeEvent(input$filterButton, {
  appliedfilter <- paste0(input$filterVar, "(", input$filterGroup,")")
  if (is.null(file$allfilters)) {
    file$allfilters <- paste("Selected Filters:", appliedfilter)
  } else {
    file$allfilters <- paste(file$allfilters, "&", appliedfilter)
  }
 })

# Print out filter variables in global object
output$selectedfilters <- renderText({
  filteroutput <- file$allfilters
  print(filteroutput)
  return(filteroutput)
})

2) The error is in the content function you pass to downloadHandler. The parameter is called file, which shadows the file reactiveValues. This works:

  ## Save out case data file
  output$downloadData <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(filetarget) {
      write.csv(file$dfSource, filetarget)
    }
  )

PS ad 1: It might be better to store the filters, instead of storing the filtered data frame and a string listing the filters. If your users change their mind, they have to start over from the beginning, but if you store the filters you can have a table or similar that allows deleting/editing individual filters. You could just store a list of two-element vectors, then iterate though the list to filter the data.