Best way to handle filtering NA values with pickerInput

I am working on a shiny app that has a reactive df which I am using to render a dt data table. My issue is that on my sidebar pickerInput filters I am using:

choices = unique(COLUMN_NAME)

When there is NA values in the column that gets turned into "NA" on the filter so when I use:

filter(COLUMN_NAME %in% input$column_name)

In the reactive df the NA values do not filter correctly. What is the best way around this? Should I recode any NA values to a string or should I work around fixing the sidebar filter so that NA filters to truly NA values?

Welcome to the community @para239! I believe the answer to your question is a matter of personal preference. Below is an example app that illustrates three different ways of handling an NA in the pickerInput. The app produces a count of species from the starwars data set based on the selection (defaulted to "Droid" and NA).

  • In the first case, the data and filter are left as-is, which results in no count for NA.
  • The second cased recodes NA values in the reactive data frame to "NA".
  • The final case does not recode NAs, rather checks for "NA" in the selection and, if present, updates the filter statement to include NAs via filter(... | is.na(species))
library(shiny)
library(tidyverse)
library(shinyWidgets)

mydata = starwars 

ui <- fluidPage(
  br(),
  pickerInput('selection', 
              'Select Species',
              choices = unique(mydata$species),
              selected = c('Droid', NA),
              multiple = T
              ),
  br(),
  HTML('<h4>Use data as-is with "regular" filter (NAs overlooked)</h4>'),
  tableOutput('mytable'),
  br(),
  br(),
  HTML('<h4>Recoded NA to "NA" string</h4>'),
  tableOutput('mytable_recode'),
  br(),
  br(),
  HTML('<h4>NA not recoded but included via logic</h4>'),
  tableOutput('mytable_no_recode')
)

server <- function(input, output, session) {
  
  # use the data as-is with a regular filter
  df = reactive({
    req(input$selection)
    
    count(mydata, species) %>%
      filter(species %in% input$selection)
  })
  
  output$mytable = renderTable({
    df()
  })
  
  # handle NA by recoding NA values to "NA"
  df_recode = reactive({
    req(input$selection)
    
    count(mydata, species) %>%
      mutate(species = ifelse(is.na(species), 'NA', species)) |>
      filter(species %in% input$selection)
  })
  
  output$mytable_recode = renderTable({
    df_recode()
  })
  
  # how to include NA without recoding
  df_no_recode = reactive({
    req(input$selection)
    
    out = count(mydata, species)
    
    # if NA in selection, include an OR (|) in the filter
    if('NA' %in% input$selection) {
      out = out %>%
        filter(species %in% input$selection | is.na(species))
    } else {
      out = out %>%
        filter(species %in% input$selection)
    }
    
    out
    
  })
  
  output$mytable_no_recode = renderTable({
    df_no_recode()
  })
  
}

shinyApp(ui, server)