shiny user input and process data before making plot

This code below may be too complex for anyone to want to delve into, but my main problem is:

  1. user inputs data in a shiny app textarea box
  2. data is processed with R code
  3. processed data is used to create a scatterplot in which brushing provides a table of brushed points.

I have tried many things like rearranging the code in various ways. Using eventReactive and observeEvent to delay or trigger some code, but after many attempts nothing seems to work and I am beginning to wonder if the above three steps are possible with shiny because I can get it to work if I pre-enter what the user is supposed to enter, but now when the user enters the data and clicks the event button, nothing happens.

The code connects to an SQLite database that is 61MB which I can upload if anyone is interested. Without the database the code will not work, but perhaps someone can see a logical error in the way I have written the code and that or any suggestions would be very helpful. 
library(shiny)
library(ggplot2)
library(stringr)
library(DBI)
library(dplyr)
library(RSQLite)
library(tibble)
library(ggraph)

ui <- fluidPage(plotOutput("plot1", height = 450, brush = "plot_brush"), verbatimTextOutput("brush_info"),
                textAreaInput("text", label = h3("AGI input"), value = ""),
                actionButton(inputId = "addAGIs", label = "Submit AGI's"),
                actionButton(inputId = "writetable", "Write tabel to text"))
# The above code does 2 things:
# 1) save space for plot1 with ability to brush points on plot (first line of code)
# 2) put a textarea box for input of AGI list (one AGI string per line). (second line of code)
# 	example of inputted text in the textarea box:
# AT4G31950
# AT5G24110
# AT1G26380
# AT1G05675

server <- function(input, output) {
  
  common <- list();  # declare 5 lists that will be used in the below for loop.
  percnt <- list(); 
  comatgs <- list(); 
  mylpercen <- list(); 
  atgs <- list(); 
  
  observeEvent(input$addAGIs, {output$value <- renderText({ input$text ;  
  myenter <- as.vector(str_split(input$text, "\n", simplify = TRUE))  
  })
  })
  # Upon clicking the "Submit AGI's" button, the above code renders text inputted in textarea input box (the list of AGIs such as in example above), and in the next line of code makes a vector of the inputted text for later use in this app. It would take the above example of inputted text and return : "AT4G31950" "AT5G24110" "AT1G26380" "AT1G05675"							
  
  data <- eventReactive(input$addAGIs, {
    # The code down to output$plot1 seems to work by itself (taken out of this context). 
    # This code depends on having the 'myenter' vector which comes from data entered by the user in the textarea box, so I have put it in 'eventReactive' so that the user can enter the AGI list and this code will not be processed until after the 'myenter' vector has been made.	
    # It connects with an SQLite database (61MB size). The database is composed of 575 tables. Each table can be thought of as having a single column (field) with a column header. The column contains AGI identifiers (e.g. AT1G89790, similar to what is inputted in the textarea box), but of various lengths, say anywhere between 50 to 3000 AGI's (rows) in a particular table. The code intersects the inputted list of AGIs (specifically, the vector 'myenter') with the database table. It then calculates the percentage of the inputted list that intersected (i.e. AGI's common to both input and database table) as well as the percentage of the table that was common. It does this for each of the 500 or so tables in the SQLite database and then makes a dataframe (mydf), e.g. str(mydf) would give you something like this:
    #'data.frame':	575 obs. of  4 variables:
    #  $ atg         : Factor w/ 394 levels "AT1G45249","AT2G36270",..: 1 2 2 3 3 4 5 6 6 7 ...
    #$ numbComElem : int  1 5 5 5 11 2 11 1 1 5 ...
    #$ percenofall : num  0.0473 0.1194 0.1301 0.2272 0.1436 ...
    #$ percenmylist: num  5 25 25 25 55 10 55 5 5 25 ...
    # mydf2 simply adds rownames to the above mydf, and is present in the code most likely do to my poor R coding skills. mydf2 looks like this:
    #str(mydf2)
    #'data.frame':	575 obs. of  5 variables:
    #  $ rowname     : chr  "ABF2_col_v3a" "ABI5_col_v3h" "ABI5_colamp_v3b" "ABR1_col_a" ...
    #$ atg         : Factor w/ 394 levels "AT1G45249","AT2G36270",..: 1 2 2 3 3 4 5 6 6 7 ...
    #$ numbComElem : int  1 5 5 5 11 2 11 1 1 5 ...
    #$ percenofall : num  0.0473 0.1194 0.1301 0.2272 0.1436 ...
    #$ percenmylist: num  5 25 25 25 55 10 55 5 5 25 ...
  con <- dbConnect(RSQLite::SQLite(), dbname="DAPSeq_genes.db") 
  listOfTables <- dbListTables(con)  
  for(i in listOfTables){p1 = tbl(con, i)
  dd  <-  as.data.frame(p1)          
  atgs[[i]] <- dbListFields(con, i)  
  z <- nrow(dd) 
  y <- intersect(dd[,1], myenter)
  myll <- length(myenter)          
  comatgs[[i]] <- intersect(dd[,1], myenter)  
  x <- length(y)  
  pcent <- (x / z) * 100   
  common[[i]] <- x   
  percnt[[i]]<- pcent; 
  mylpcen <- ((x / myll) * 100) 
  mylpercen[[i]] <- mylpcen   }   
  
  mydf <- do.call(rbind, Map(data.frame, atg=atgs, numbComElem=common, percenofall=percnt, percenmylist=mylpercen)) 
  mydf2 <- rownames_to_column(mydf)
  rownames_to_column(mydf) %>% arrange(desc(numbComElem)) %>% slice(1:50) 
  rownames_to_column(mydf) %>% arrange(desc(percenofall)) %>% slice(1:50)
  })  # ends the eventReactive code block
  
  output$plot1 <- renderPlot({     
    ggplot(data=mydf2, aes(x=percenmylist, y=percenofall)) + geom_point() + geom_text(label= mydf2$rowname, size = 2, nudge_x = 0.8, nudge_y = 0.05) + xlab("% of input list") + ylab("% of DAPSeq TF list")
                             })
  # The code below outputs plot1 and allows for brushing of points. 
  # When points are bushed a table of brushed points appear below the plot. 
  xn <- NULL
  output$brush_info <- renderPrint({x <<- brushedPoints(mydf2, input$plot_brush, xvar = "percenmylist", yvar = "percenofall")
 
                                  })
  # The code below will print out the table when the "writetable" button is clicked. The table is printed to a tab-delimited text file named MySelection.txt. 							  
  observeEvent(input$addAGIs, {write.table(x, file = "MySelectipon.txt", sep = "\t", row.names = FALSE, col.names = TRUE)})
  xn 
}
shinyApp(ui, server)

eventReactive() returns a function, so to get the value it returns you need to use data(). Something like

Notice I have replaced mydf2 with data().
I am not sure just what the function actually returns, as I do not understand everything about your code. If you need data() to return more than one object (mydf and mydf2 ?), you can wrap them in a list.

Thank you, FJCC.
Sorry about the code. It is difficult to follow.
I made the change, but I get the following error:
Warning: Error in as.vector: object 'myenter' not found
I don't understand that error, because 'myenter' should be made when the event button is clicked. The 'myenter' vector is the user inputted data which is necessary for the rest of the code.

A picture may help, so this is what it initally looks like.

And after the data is entered by the user it should look like this:

Some comments on your code. Keep in mind that I am very far from being an expert on shiny, so my comments may not be correct.
I don not think you can use observeEvent() the way you are trying to. Quoting from the documentation on observe:

An observer is like a reactive expression in that it can read reactive values and call reactive expressions, and will automatically re-execute when those dependencies change. But unlike reactive expressions, it doesn't yield a result and can't be used as an input to other reactive expressions. Thus, observers are only useful for their side effects (for example, performing I/O).

I do not think you want to render your input$text. That is for display purposes. You want to use it to modify the data.

You assign the renderText() of input$text to output$value but the ui does not seem to have any output called value.
Here is a super simple version of the sort of thing I think you are trying to do. Enter any two letters from (A, B, C) separated by a line feed in the textAreaInput() and you will get a plot of the second entry versus the first.

library(shiny)

df <- data.frame(A = seq(1,10), B = seq(11, 20), C = seq(21, 30))

ui <- fluidPage(plotOutput("plot1", height = 450), 
                textAreaInput("text", label = h3("AGI input"), value = ""),
                actionButton(inputId = "addAGIs", label = "Submit AGI's"))


server <- function(input, output) {
  
  data <- eventReactive(input$addAGIs, {
    myenter <- as.vector(str_split(input$text, "\n", simplify = TRUE))
    df[, myenter]
  })  # ends the eventReactive code block
  
  output$plot1 <- renderPlot({     
    plot(data()[,1], data()[, 2])
  })

}
shinyApp(ui, server)

Here is a different version where data() returns a list

library(shiny)

df <- data.frame(A = seq(1,10), B = seq(11, 20), C = seq(21, 30))

ui <- fluidPage(plotOutput("plot1", height = 450), 
                textAreaInput("text", label = h3("AGI input"), value = ""),
                actionButton(inputId = "addAGIs", label = "Submit AGI's"))


server <- function(input, output) {
  
  
  data <- eventReactive(input$addAGIs, {
    myenter <- as.vector(str_split(input$text, "\n", simplify = TRUE))
    list(datframe = df[, myenter], Names = myenter)
  })  # ends the eventReactive code block
  
  output$plot1 <- renderPlot({     
    plot(data()[["datframe"]][,1], data()[["datframe"]][, 2], 
         xlab = data()[["Names"]][1], ylab = data()[["Names"]][2])
  })

}
shinyApp(ui, server)

Thank you very much for this code. Your code works very well, however, I think there is one important difference between your code and what I would like to do and this difference is what I think is preventing my code from working.
With your code, the data frame is made first before any shiny code is processed and the user entry is to choose one of the columns of the already made data frame.
However, in my code the user entry is needed to construct the data frame. The data frame can not be made without the user entered data. So, the app has to wait for the user to enter data, then construct the data frame and finally plot the data frame. Having the app follow that specific sequence, I think, is my main problem.
The UI needs to run first to accept user input, but the server has to execute its code only after the input button has been clicked. I think the eventReactive allows the app to skip over what is in eventReactive, but then it goes to making the data frame and when that happens it can not work because it is lacking a variable; the variable that is the input data. I have tried putting all but the brush and write table inside the eventReactive, but then nothing happens after the data is entered and the button is clicked.
It seems to me that this is general problem. With python you can have the user enter and the code will wait until the user enters data or hits return. Can Shiny be used when the user needs to input data before that data is processed and plotted ? Maybe in R there is some way to delay the code from running until data has been entered by the user ? If so, could that be used with Shiny ?

Here is an example that pulls in data from an external web site and allows the user to choose what is plotted on the x and y axis. When the button is clicked, the chosen subset of the imported data is made and a plot that can be brushed for data points is shown. Making the subset is not really necessary but I wanted to show some sort of manipulation of the data.

library(shiny)
library(ggplot2)

ui <- fluidPage(plotOutput("plot1", height = 450, brush = "plot_brush"), 
                h4("Table of Selected Points"),
                verbatimTextOutput("brush_info"), 
                selectInput("x_values", label = "X", 
                            choices = c("beer_servings", "spirit_servings", "wine_servings",
                                        "total_litres_of_pure_alcohol")),
                selectInput("y_values", label = "Y",
                            choices = c("beer_servings", "spirit_servings", "wine_servings", 
                                        "total_litres_of_pure_alcohol")),
                actionButton(inputId = "addAGIs", label = "Update Plot"))


server <- function(input, output) {
  
  datList <- eventReactive(input$addAGIs, {
    df <- read.csv(file = "https://raw.githubusercontent.com/fivethirtyeight/data/master/alcohol-consumption/drinks.csv")
    list(datFrame = df[, c(input$x_values, input$y_values)], X = input$x_values, Y = input$y_values)
  
  })  # ends the eventReactive code block
  
  observeEvent(input$addAGIs,{
  output$plot1 <- renderPlot({     
    ggplot(datList()[["datFrame"]], aes_string(datList()[["X"]], datList()[["Y"]])) + geom_point()
  })
  output$brush_info <- renderPrint({
    brushedPoints(datList()[["datFrame"]], input$plot_brush)
  })
  
  })
  
}
shinyApp(ui, server)

Shiny applications not supported in static R Markdown documents

Created on 2019-03-18 by the reprex package (v0.2.1)

1 Like

Thank you very much FJCC for sticking with me on this !
I like your code; very nice !
It must have given me some insight because I got my code working ! I got rid of the eventReactive and just used an observeEvent. The observeEvent enclosed all but the output$brush_info and print the table.
I also deleted the renderText and just had the myenter vector created inside observeEvent with input$text. The whole code is below. Unfortunately it needs the SQLite database on my computer to run, but I am very happy that it is working now ! Thanks again for your help.

library(shiny)
library(ggplot2)
library(stringr)
library(DBI)
library(dplyr)
library(RSQLite)
library(tibble)
library(ggraph)

ui <- fluidPage(plotOutput("plot1", height = 450, brush = "plot_brush"), verbatimTextOutput("brush_info"),
                textAreaInput("text", label = h3("AGI input"), value = ""),
                hr(),
                fluidRow(column(3, verbatimTextOutput("value"))),
                actionButton(inputId = "addAGI", label = "Submit AGI's"),
                actionButton(inputId = "writetable", label = "Write table to file"))

server <- function(input, output) {
  
  observeEvent(input$addAGI, {
    myenter <- as.vector(str_split(input$text, "\n", simplify = TRUE))
    
    con <- dbConnect(RSQLite::SQLite(), dbname="DAPSeq_genes.db") 
    listOfTables <- dbListTables(con)
    common <- list();  
    percnt <- list(); 
    comatgs <- list(); 
    mylpercen <- list(); 
    atgs <- list();
    
    for(i in listOfTables){p1 = tbl(con, i)
    dd  <-  as.data.frame(p1) ;          
    atgs[[i]] <- dbListFields(con, i) ; 
    z <- nrow(dd); 
    y <- intersect(dd[,1], myenter)	
    myll <- length(myenter);           
    comatgs[[i]] <- intersect(dd[,1], myenter);  
    x <- length(y) ; 
    pcent <- (x / z) * 100 ;   
    common[[i]] <- x   ; 
    percnt[[i]]<- pcent; 
    mylpcen <- ((x / myll) * 100) ; 
    mylpercen[[i]] <- mylpcen   }   
    
    mydf <- do.call(rbind, Map(data.frame, atg=atgs, numbComElem=common, percenofall=percnt, percenmylist=mylpercen)); 
    mydf2 <- rownames_to_column(mydf); 
    rownames_to_column(mydf) %>% arrange(desc(numbComElem)) %>% slice(1:50); 
    rownames_to_column(mydf) %>% arrange(desc(percenofall)) %>% slice(1:50);  
    
    output$plot1 <- renderPlot( {ggplot(data=mydf2, aes(x=percenmylist, y=percenofall)) + geom_point() + geom_text(label= mydf2$rowname, size = 2, nudge_x = 0.8, nudge_y = 0.05) + xlab("% of input list") + ylab("% of DAPSeq TF list")})
    
  })
    
  xn <- NULL
  output$brush_info <- renderPrint({xn <<- brushedPoints(mydf2, input$plot_brush, xvar = "percenmylist", yvar = "percenofall")
  xn  }) 
                           
  observeEvent(input$writetable, {write.table(xn, file = "MySelectipon.txt", sep = "\t", row.names = FALSE, col.names = TRUE)})
}

shinyApp(ui, server)

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.