Search code examples
rdataframeshinysubsetaction-button

Shiny data frame subset based on character input converted to numerical vector


I'm trying to subset data frame in Shiny app based on character input, but only after action button has been pushed. Here how this looks like in UI:

textInput("cases", "Select cases", value = ""),
    actionButton("submit", "Submit", icon = icon("refresh"), 
                 style="float:right"),

The idea is to let user choose what cases of data frame should be shown on the plot (i.e. "11,23,17,102"). In order to convert character vector into numeric, and subsequently subset data frame based on that criteria, I've tried to use next Server code:

    cases <- eventReactive(input$submit, {as.numeric(unlist(strsplit(input$cases,",")))})
    df <- df[cases, ]

After running application and uploading data set I get Error:invalid subscript type 'closure' Without these lines, application works fine on whole data set. Furthermore, the same code, but with non-reactive values works fine outside Shiny environment:

cases <- "3,6,11,55,60" # example of user input 
cases <- unlist(strsplit(cases,","))
cases <- as.numeric(cases)
df <- df[cases, ]

Any help on this would be appreciated.

In response to @socialscientist I am providing a wider scope of my code:

    df <- read.csv(infile$datapath, header = TRUE, stringsAsFactors = FALSE)

    # Subset data frame based on mode selection
    mode <- input$mode
    df <- df[df$Mode == mode, ]
    
    # Take original values of wavelength as x-axis
    df_x <- read.csv(infile$datapath, header = FALSE)
    x_val <- as.vector(df_x[1, 11:2059])
    
    # Remove unnecessary columns
    df <- df[, 11:2059]
    
    # Data frame cases subset based on user input
    df <- eventReactive(input$submit, {
                cases <- as.numeric(unlist(strsplit(input$cases,",")))
                a <- df[cases(), ]
                return(a)
                })
    
    # Transpose and melt data
    t_df <- transpose(df)
    t_df <- cbind(t(x_val), t_df)
    colnames(t_df)[1] <- "wave"
    final_data <- as.data.table(reshape2::melt(t_df, id='wave'))

After running this code I get an error Error in transpose: l must be a list. I am aware that cases() is not function anymore, and that eventReactive now is producing df() function, but I do not know how to use it.


Solution

  • I'm not certain if this is going to resolve all issues, but I'm going to infer that the code has nested observe/reactive blocks (or something else equally not-right). Here's a working example of allowing comma-separated numbers and a submit button to subset a frame.

    library(shiny)
    ui <- fluidPage(
      textInput("cases", "Select cases", value = ""),
      actionButton("submit", "Submit", icon = icon("sync")),
      tableOutput("tbl")
    )
    server <- function(input, output, session) {
      cases <- eventReactive(input$submit, {
        out <- strsplit(input$cases, ",")[[1]]
        out <- suppressWarnings(as.numeric(out[nzchar(out)]))
        validate(
          need(!anyNA(out), "Input one or more numbers separated by commas")
        )
        out
      })
      output$tbl <- renderTable({
        if (length(cases())) mtcars[cases(),] else mtcars
      })
    }
    shinyApp(ui, server)
    

    Notes:

    • Error resolution: while out[nzchar(out)] will silently remove empty strings caused by two consecutive commas (e.g., 1,,3) without error, the !anyNA(out) will cause that stop cascading reactivity, instead replacing all components that use cases() with the string Input one or more .... This means that 1,,3 will work without complaint, but 1,a,3 will fail and be politely noisy.

    • This example chooses to show all rows by default. If instead you want nothing shown until there is valid input in cases, then replace the last reactive block here with:

        output$tbl <- renderTable({ mtcars[cases(),] })