Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

r shiny: eventReactive is not reacting when the button is pressed

Tags:

r

reactive

shiny

Below is my code. It might seem a bit long but actually it's a VERY simple app.

The user is supposed to upload a tiny data frame (x.csv if you are in the US or x_Europe.csv if you are in Europe). Then the user should click on the button to start calculations. And then at the end the user should be able to download the results of those calculations as a data frame.

My problem: after I upload the file, when I click on the 'do_it' action button - nothing happens. I can see it because nothing is being printed to my console. WHY? After all, my function 'main_calc' should be eventReactive to input$do_it? Why do all the calculations inside main_calc start happening ONLY after the user tries to download the results?

Important: It is important to me to keep the 'Data' function separately from main_calc.

Thank you very much!

First, generate one of these 2 files in your working directory:

# generate file 'x.csv' to read in later in the app:
write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)  # US file
write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)

This is the code for the shiny app:

library(shiny)

ui <- fluidPage(
  # User should upload file x here:
  fileInput("file_x", label = h5("Upload file 'x.csv'!")),
  br(),
  actionButton("do_it", "Click Here First:"),
  br(),
  br(),
  textInput("user_filename","Save your file as:", value = "My file x"),
  downloadButton('file_down',"Save the output File:")
)

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

  #----------------------------------------------------------------------
  # Function to read in either European (csv2) or American (csv) input:
  #----------------------------------------------------------------------

  ReadFile <- function(pathtofile, withheader = TRUE){

    test <- readLines(pathtofile, n = 1)  
    if (length(strsplit(test, split = ";")[[1]]) > 1) {
      print("Reading European CSV file")
      outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
                      europe.file = 1)
    } else {
      print("Reading US CSV file")
      outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
                      europe.file = 0)
    }
    return(outlist)
  }

  #----------------------------------------------------------------------
  # Data-related - getting the input file
  #----------------------------------------------------------------------  

  Data <- reactive({

    print("Starting reactive function 'Data'")
    # Input file:
    infile_x <- input$file_x
    myx <- ReadFile(infile_x$datapath)$myinput

    # European file?
    europe <- ReadFile(infile_x$datapath)$europe.file

    print("Finishing reactive function 'Data'")
    return(list(data = myx, europe = europe))

  })

  #----------------------------------------------------------------------
  # Main function that should read in the input and 'calculate' stuff
  # after the users clicks on the button 'do_it' - takes about 20 sec
  #----------------------------------------------------------------------

  main_calc <- eventReactive(input$do_it, {

    req(input$file_x)

    # Reading in the input file:
    x <- Data()$data
    print("Done reading in the data inside main_calc")

    # Running useless calculations - just to kill time:

    myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
    print("Starting calculations")

    for (i in seq_len(10)) {
      set.seed(12)
      mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
      temp <- solve(mymatr) %*% myvector
    }

    print("Finished calculations")

    # Creating a new file:
    y <- temp
    result = list(x = x, y = y)
    print("End of eventReactive function main_calc.")
    return(result)
  })   # end of main_calc

  #----------------------------------------------------------------------
  # The user should be able to save the output of main_calc as a csv file
  # using a string s/he specified for the file name:
  #----------------------------------------------------------------------

  output$file_down <- downloadHandler(
    filename = function() {
      paste0(input$user_filename, " ", Sys.Date(), ".csv") 
    },
    content = function(file) {
      print("Europe Flag is:")
      print(Data()$europe)

      if (Data()$europe == 1) {
        x_out <- main_calc()$x
        print("Dimensions of x in downloadHandler are:")
        print(dim(x_out))        
        write.csv2(x_out, 
                   file,
                   row.names = FALSE)
      } else {
        x_out <- main_calc()$x
        print("Dimensions of x in downloadHandler are:")
        print(dim(x_out))
        write.csv(x_out, 
                  file,
                  row.names = FALSE)
      }
    }
  )


}  # end of server code  

shinyApp(ui, server)
like image 626
user3245256 Avatar asked Feb 21 '18 16:02

user3245256


People also ask

When to use observeEvent shiny?

Use observeEvent whenever you want to perform an action in response to an event. (Note that "recalculate a value" does not generally count as performing an action--see eventReactive for that.)


2 Answers

Below is the solution - based on MrFlick's suggestions:

# generate file 'x.csv' to read in later in the app:
# write.csv(data.frame(a = 1:4, b = 2:5), "x.csv", row.names = F)
# write.csv2(data.frame(a = 1:4, b = 2:5), "x_Europe.csv", row.names = F)

library(shiny)
library(shinyjs)

ui <- fluidPage(
  # User should upload file x here:
  fileInput("file_x", label = h5("Upload file 'x.csv'!")),
  br(),
  actionButton("do_it", "Click Here First:"),
  br(),
  br(),
  textInput("user_filename","Save your file as:", value = "My file x"),
  downloadButton('file_down',"Save the output File:")
)

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



  #----------------------------------------------------------------------
  # Function to read in either European (csv2) or American (csv) input:
  #----------------------------------------------------------------------

  ReadFile <- function(pathtofile, withheader = TRUE){

    test <- readLines(pathtofile, n = 1)  
    if (length(strsplit(test, split = ";")[[1]]) > 1) {
      print("Reading European CSV file")
      outlist <- list(myinput = read.csv2(pathtofile, header = TRUE),
                      europe.file = 1)
    } else {
      print("Reading US CSV file")
      outlist <- list(myinput = read.csv(pathtofile, header = TRUE),
                      europe.file = 0)
    }
    return(outlist)
  }


  #----------------------------------------------------------------------
  # Data-related - getting the input file
  #----------------------------------------------------------------------  

  Data <- reactive({


    print("Starting reactive function Data")
    # Input file:
    infile_x <- input$file_x
    myx <- ReadFile(infile_x$datapath)$myinput

    # European file?
    europe <- ReadFile(infile_x$datapath)$europe.file

    print("Finishing reactive function 'Data'")
    return(list(data = myx, europe = europe))

  })

  #----------------------------------------------------------------------
  # Main function that should read in the input and 'calculate' stuff
  # after the users clicks on the button 'do_it' - takes about 20 sec
  #----------------------------------------------------------------------


  # Creating reactive Values:
  forout_reactive <- reactiveValues()

  observeEvent(input$do_it, {

    print("STARTING observeEvent")

    req(input$file_x)

    # Reading in the input file:
    x <- Data()$data
    print("Done reading in the data inside observeEvent")

    # Running useless calculations - just to kill time:

    myvector <- matrix(unlist(x), ncol = 1, nrow = 1000)
    print("Starting calculations")

    for (i in seq_len(10)) {
      set.seed(12)
      mymatr <- matrix(abs(rnorm(1000000)), nrow = 1000)
      temp <- solve(mymatr) %*% myvector
    }  # takes about 22 sec on a laptop

    print("Finished calculations")

    # Creating a new file:
    y <- temp
    forout_reactive$x = x
    forout_reactive$y = y
    print("End of observeEvent")
  })   # end of main_calc

  #----------------------------------------------------------------------
  # The user should be able to save the output of main_calc as a csv file
  # using a string s/he specified for the file name:
  #----------------------------------------------------------------------

  output$file_down <- downloadHandler(
    filename = function() {
      paste0(input$user_filename, " ", Sys.Date(), ".csv") 
    },
    content = function(file) {
      print("Europe Flag is:")
      print(Data()$europe)

      if (Data()$europe == 1) {
        y_out <- forout_reactive$y
        print("Dimensions of y in downloadHandler are:")
        print(dim(y_out))        
        write.csv2(y_out, 
                   file,
                   row.names = FALSE)
      } else {
        y_out <- forout_reactive$y
        print("Dimensions of y in downloadHandler are:")
        print(dim(y_out))
        write.csv(y_out, 
                  file,
                  row.names = FALSE)
      }
    }
  )


}  # end of server code  

shinyApp(ui, server)
like image 120
user3245256 Avatar answered Sep 29 '22 02:09

user3245256


Here is a simple app that may help elucidate how eventReactive() works:


library(shiny)

run_data <- function() {
  paste0("Random number generated in eventReactive: ", runif(1))
}

ui <- basicPage(
  actionButton("run1", "Invalidate eventReative()"),
  actionButton("run2", "Trigger observeEvent()"),
  verbatimTextOutput("data")
)

server <- function(input, output, session) {
  
  # Initialize reactiveValues list 
  # to use inside observeEvent()
  rv <- reactiveValues(data = NULL)
  
  # This eventReactive() doesn't run when run1 button is
  # clicked. Rather, it becomes invalidated. Only when
  # data() (the reactive being returned) is actually 
  # called, does the expression inside actually run.
  # If eventReactive is not invalidated by clicking run1
  # then even if data() is called, it still won't run.
  data <- eventReactive(input$run1, {
    
    showNotification("eventReactive() triggered...")
    
    run_data()
    
  })
  
  
  # Every time run2 button is clicked,
  # this observeEvent is triggered and
  # will run. If run1 is clicked before run2,
  # thus invalidating the eventReactive
  # that produces data(), then data() will
  # contain the output of run_data() and
  # rv$data will be assigned this value.
  observeEvent(input$run2, {
    
    showNotification("observeEvent() triggered")
    
    rv$data <- data()
    
  })
  
  
  # Renders the text found in rv$data
  output$data <- renderText({
    
    rv$data
    
  })

}

shinyApp(ui, server)

In this example, run1 invalidates the eventReactive(), and run2 triggers the observeEvent() expression. In order for the data (in this case just a random number) to print, run1 must be clicked prior to run2.

The key takeaway is that the input(s) (buttons) that eventReactive() listens to don't trigger eventReactive(). Instead, they invalidate eventReactive() such that when the output from eventReactive() is required, then the expression inside eventReactive() will run. If it is not invalidated or the output is not needed, it will not run.

like image 31
Giovanni Colitti Avatar answered Sep 29 '22 02:09

Giovanni Colitti