adl
adl

Reputation: 1441

r - create reactive filter that is applied to an uploaded csv file in shiny

I'm trying to create a shiny app in which a user would be able to upload a .csv file, and then apply a filter on that dataframe and then make a plot using ggplot2. Unfortunately I stumbled upon a problem when trying to create a select input menu from a variable coming from the .csv that is supposed to be uploaded.

To create a simple example we can export the diamonds dataset from ggplot2 as a .csv file:

write.csv(diamonds, "diamonds.csv")

Then to create the shiny app:

library(shiny)
library(shinyWidgets)
library(ggplot2)

ui <- fluidPage(
    titlePanel("test shiny"),

    # Sidebar with a slider input for number of bins
    sidebarLayout(sidebarPanel(
        fileInput('file1', 'Choose file to upload',
                  accept = c(
                      'text/csv',
                      'text/comma-separated-values',
                      'text/tab-separated-values',
                      'text/plain',
                      '.csv',
                      '.tsv'
                  )
        ),
        tags$hr(),
        pickerInput(
            inputId = "caratx",
            label = "Choose carat",
            choices = c("Select all", unique(user_data$carat)),
            multiple = TRUE
        ),
        selectInput(
            inputId = "clarityx",
            label = "Choose distance: ",
            choices = unique(user_data$clarity)
        )
    ),
    mainPanel(plotOutput("endplot"))
    ))

# Define server logic 
server <- function(input, output) {

    output$endplot <- renderPlot({
        inFile <- input$file1

        if (is.null(inFile))
            return(NULL)

        user_data <- read.csv(inFile$datapath, header = T,
                              sep = ",", quote = input$quote)

        validate(
            need(input$entityx, 'Please select at least one carat'),
            need(input$indicatorx, 'Please select at least one clarity')
        )

        if (input$caratx %in% "Select all") {
            user_data <- user_data %>%
                filter(carat %in% input$caratx)
        } else {
            user_data <- user_data %>%
                filter(carat %in% input$caratx) %>%
                filter(clarity %in% input$clarityx) 
        }

        user_data %>% 
            ggplot(aes(x = `cut`)) +
            geom_point(aes(y = price), color = "red") +
            geom_point(aes(y = depth), color = "blue")
        })
}

# Run the application 
shinyApp(ui = ui, server = server)

This is the output:

Error in unique(user_data$carat) : object 'user_data' not found

Is there a way to make this work ?

Many thanks !

Upvotes: 0

Views: 1695

Answers (2)

DeanAttali
DeanAttali

Reputation: 26333

The actual question you're asking should be solved by turning user_data into a reactive and rendering the inputs using uiOutput+renderUI, as @kwiscion correctly commented.

But unfortunately, your code has a few other errors that make it not reproducible:

  • The sample dataset should be diamonds, not cars
  • input$quote doesn't exist in your code, so I removed the reference to it
  • You need to include library(dplyr) for some of the functions in your code
  • input$entityx and input$indicatorx don't exist

The above 4 errors are not related to your question, but made it much harder and longer to answer your question. In the future, please try to make sure that the code you post is correct and reproducible.

Below is an answer to your question that solved the 4 issues above and implements the two suggestions by @kwiscion

library(shiny)
library(shinyWidgets)
library(ggplot2)
library(dplyr)

ui <- fluidPage(
  titlePanel("test shiny"),

  # Sidebar with a slider input for number of bins
  sidebarLayout(sidebarPanel(
    fileInput('file1', 'Choose file to upload',
              accept = c(
                'text/csv',
                'text/comma-separated-values',
                'text/tab-separated-values',
                'text/plain',
                '.csv',
                '.tsv'
              )
    ),
    tags$hr(),
    uiOutput("caratx_input"),
    uiOutput("clarityx_input")
  ),
  mainPanel(plotOutput("endplot"))
  ))

# Define server logic 
server <- function(input, output) {
  file_data <- reactive({
    req(input$file1)
    read.csv(input$file1$datapath, header = TRUE,
             sep = ",")
  })

  output$caratx_input <- renderUI({
    req(file_data())
    pickerInput(
      inputId = "caratx",
      label = "Choose carat",
      choices = c("Select all", unique(file_data()$carat)),
      multiple = TRUE
    )
  })

  output$clarityx_input <- renderUI({
    req(file_data())
    selectInput(
      inputId = "clarityx",
      label = "Choose distance: ",
      choices = unique(file_data()$clarity)
    )
  })

  output$endplot <- renderPlot({
    req(file_data())

    validate(
      need(input$caratx, 'Please select at least one carat'),
      need(input$clarityx, 'Please select at least one clarity')
    )

    user_data <- file_data()
    if (input$caratx %in% "Select all") {
      user_data <- user_data %>%
        filter(carat %in% input$caratx)
    } else {
      user_data <- user_data %>%
        filter(carat %in% input$caratx) %>%
        filter(clarity %in% input$clarityx) 
    }

    user_data %>% 
      ggplot(aes(x = `cut`)) +
      geom_point(aes(y = price), color = "red") +
      geom_point(aes(y = depth), color = "blue")
  })
}

# Run the application 
shinyApp(ui = ui, server = server)

Upvotes: 5

kwiscion
kwiscion

Reputation: 596

You need to

  1. user_data a reactive expression (https://shiny.rstudio.com/tutorial/written-tutorial/lesson6/)
  2. Render pickerInput with renderUI (https://shiny.rstudio.com/articles/dynamic-ui.html).

(Full code below)

  1. When you want to calculate and later use value dependent on user input (in your case user_data) you have to make it reactive expression by putting the calculation inside reactive({ }). Then you can refer to the value like to a function, i.e. user_data(). So in your case it would be:
user_data <- reactive({
      read.csv(input$file1$datapath, header = T,
               sep = ",", quote = input$quote)
    })

user_data() %>% ...

Also, it has to be pulled out fromrenderPlot() to work as reactive.

  1. To have an input depending on a value calculated during execution, you have to have it rendered on the server side with renderUI():

ui.R

...
uiOutput('caratxui'),
...

server.R

...
output$caratxui <- renderUI({
        pickerInput(
            inputId = "caratx",
            label = "Choose carat",
            choices = c("Select all", unique(user_data()$carat)),
            multiple = TRUE
        )

    })
...

Full code:

library(shiny)
library(shinyWidgets)
library(ggplot2)

ui <- fluidPage(
    titlePanel("test shiny"),

    # Sidebar with a slider input for number of bins
    sidebarLayout(sidebarPanel(
        fileInput('file1', 'Choose file to upload',
                  accept = c(
                      'text/csv',
                      'text/comma-separated-values',
                      'text/tab-separated-values',
                      'text/plain',
                      '.csv',
                      '.tsv'
                  )
        ),
        tags$hr(),
        uiOutput('caratxui'),
        selectInput(
            inputId = "clarityx",
            label = "Choose distance: ",
            choices = unique(user_data$clarity)
        )
    ),
    mainPanel(plotOutput("endplot"))
    ))

# Define server logic 
server <- function(input, output) {
    user_data <- reactive({
      read.csv(input$file1$datapath, header = T,
               sep = ",", quote = input$quote)
    })

    output$caratxui <- renderUI({
        pickerInput(
            inputId = "caratx",
            label = "Choose carat",
            choices = c("Select all", unique(user_data()$carat)),
            multiple = TRUE
        )

    })

    output$endplot <- renderPlot({
        validate(
            need(input$entityx, 'Please select at least one carat'),
            need(input$indicatorx, 'Please select at least one clarity')
        )

        user_data() %>%
            filter(carat %in% input$caratx) %>%
            filter(clarity %in% input$clarityx | 
                    input$caratx == "Select all") %>% 
            ggplot(aes(x = `cut`)) +
            geom_point(aes(y = price), color = "red") +
            geom_point(aes(y = depth), color = "blue")
        })
}

# Run the application 
shinyApp(ui = ui, server = server)

Upvotes: 2

Related Questions