0

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.

  • 1
    Use `df[cases(),]`. The object stored out of `reactive` blocks are functions, not simple objects. – r2evans Aug 15 '22 at 01:19
  • FYI, a "closure" in R is really just a function with its defining environment. – r2evans Aug 15 '22 at 01:19
  • Thank you @r2evans for your brief response. Unfortunately, I didn't get expected result with `df[cases(),]`. Is there any trick to turn `cases()` into numeric vector? – Dejan Pljevljakusic Aug 15 '22 at 07:05
  • Best to tell us what you *did* get – socialscientist Aug 15 '22 at 07:26
  • I got the comment `Error in transpose: l must be a list`. I have provided wider scope of my code in addition to the original question. – Dejan Pljevljakusic Aug 15 '22 at 10:41
  • You're using `eventReactive(input$submit,..)` but some of the code around it looks like it is also in a reactive or observe block, nested. Don't nest, I think it never is suggested, required, and unknown if it behaves anything as intended. – r2evans Aug 15 '22 at 11:20
  • I said earlier that the result of an `event*` or `observe` block returns a function, so you needed to use `cases()` in place of `case`. Similarly, your `df` must be referenced later as `df()`, not as `df` (which is a function as well). (Your last error can be reproduced with an obviously-wrong `transpose(transpose)`.) But again, I'm inferring that your added code block is itself within a reactive block of some sort, which shouldn't be the case. If I'm inferring incorrectly, show me wrong with a small complete shiny example. – r2evans Aug 15 '22 at 11:51

1 Answers1

1

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(),] })
    
r2evans
  • 141,215
  • 6
  • 77
  • 149
  • Thank you very much for this solution. This solve my problem. I've changed code to subset data set for further plotting to `df <- if (length(cases())) df[cases(),] else df`. The only thing now is that nothing is happening before action button is pressed, but I can live with that since submitted empty input give me whole data set plotting. – Dejan Pljevljakusic Aug 15 '22 at 12:52
  • What do you mean, nothing is happening? That's the design of using the button, it is explicitly "do nothing (that depends on this) until it is pressed". Are you hoping that as soon as something is typed in the input box, it is immediately used without the button press? – r2evans Aug 15 '22 at 12:55
  • Thanks for asking. No, I hope that plot appears, with all cases included, soon as I upload data set, and that I can select cases according to this preview by typing and pressing the button. But, as I said, the whole data set can be loaded into plot when I press button with empty text input, and that works fine for me. Thanks again. – Dejan Pljevljakusic Aug 16 '22 at 10:06