Perform multiple actions upon pressing an actionButton - r

I've been struggling to get my Shiny app to perform multiple operations upon pressing a single actionButton. I've looked around a lot and haven't been able to solve it. I've tried eventReactive, observeEvent, putting everything in one block, separating them out - you can see the eventReactive and observeEvent attempts in the server.R code.
I have a button which is supposed to run some models, once pressed. Upon pressing the button, I'd like to do quite a few things:
Print a message letting the user know that something is happening
Concatenate the previously-defined user options into a .sh file and execute model prediction
Periodically check for the model output file
Once output file has been created, print "Done" and save as a global variable for further analysis
This is what I have so far (it doesn't work, the model runs [yay!] and that's it). It's particularly confusing to me that the sys command runs fine, but the "Running" message doesn't get printed. I've made sure that I haven't misspelt any var names.
Dummy python script to produce an output: Let's call it script_shiny.py
import time
import pandas as pd
# construct dummy df
d = {'col1': [1, 2], 'col2': [3, 4], 'col3': [5,6]}
df = pd.DataFrame(data=d)
time.sleep(60) # let's add a delay to simulate the 'real' situation
df.to_csv("test_out.txt",sep="\t") # create the output file to be detected
The relevant part of ui.R
*CODE TO CAPTURE PARAMS, WORKS FINE*
shiny::actionButton("button", "Run PIDGIN"), # RUN MODELS
tags$br(),
textOutput("pidginrunning"), # OUTPUT RUNNING MESSAGE
textOutput("pidgindone"), # OUTPUT DONE MESSAGE
The relevant part of server.R
# One approach - observeEvent and defining renderText and .sh file creation and running all together
observeEvent(input$button, { # OBSERVE BUTTON BEING PRESSED
output$pidginrunning <- renderText({ # LET USER KNOW ITS RUNNING - NOT WORKING
paste0("Running PIDGIN...")
})
# CREATE .SH FILE AND RUN DUMMY SCRIPT - WORKING (params previously defined)
bin_bash <- "#!/bin/bash"
output_name <<- "test_out.txt"
runline <- paste0("python script_shiny.py")
bash_file <- data.frame(c(bin_bash,runline))
write.table(bash_file,"./run_pidgin.sh",quote=F,row.names=F,col.names=F)
system("bash -i run_pidgin.sh")
})
# CHECK OUTPUT - NOT WORKING
# Defining output_name in the previous code block works, its defined as a global variable
# Other approach, use eventReactive to define a 'function' which is called separately
checkOutput <- eventReactive(input$button, {
reactivePoll(1000, session, checkFunc = function() {
if (file.exists(output_name)) # check for the output file
assign(x = "preds", # Read it in if so, and define global variable
value = read.csv(output_name,header=T,sep="\t"),
envir = .GlobalEnv)
paste0("Done. Please move onto the Results tab.")
})
})
output$pidgindone <- renderText({ # PRINT DONE MESSAGE SO USER KNOWS TO MOVE ON
checkOutput()
})
(Hope that counts as minimum example. As a proxy I suppose you can generate a dummy .sh file which produces a .txt file table, instead of running the models).
Cheers for your time and help.
EDIT: added example script which creates an output which should be detected

Related

R Shiny: Why can't functions from source files access global variables when running as an app?

I have built a Shiny dashboard which is nearly complete.
I have multiple source files containing various functions, some which reference global variables which are defined in the main R code, without being passed them as arguments directly.
I have been building it and testing/debugging it by running segments of code, and manually running the shinyapp function to launch it. When I do this, the app works as expected. The functions from the source files can read the global variables.
However, when I clear the workspace and save the code and use the "Run App" feature to run the whole thing at once, it fails because the source file functions can no longer find the global variables, even though the code which creates these variables in the global environment comes before any code which calls these functions.
Note that if I've already run the section of code manually that creates the global variables and run the app without clearing the workspace, the functions are able to read them.
Does anyone know why this is, and how I can get around it?
In the reproducible example below, the global variable is given the value "Hello_World". The function test_function() simply returns the value of the global variable which is then used as a heading in the UI.
MAIN R CODE
# Import packages.
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
library(tidyverse)
library(dtplyr)
# Global Variables
global_var <- "Hello_World"
# Directory
directory <- ''
# Source function stored in separate file.
source(paste0(directory,"Test Function.R"))
# UI
ui <- dashboardPage(
dashboardHeader(),
dashboardSidebar(
sidebarMenu(
id = "tabs",
menuItem("Test", tabName="tab_data",icon=icon("table"))
)
),
dashboardBody(
tabItem("tab_data",
fluidPage(
fluidRow(column(width=12,
h4(test_function())
))
)
)
)
)
# Server
server <- function(input, output, session) {
}
shinyApp(ui, server)
"TEST FUNCTION.R"
test_function <- function(){
return(global_var)
}
You will need to create these as two files in the home directory. The code as it is fails and throws the error:
Error in test_function() : object 'global_var' not found
If I change the last line of the code to the following, it allows the code to be run manually in segments. If I highlight the whole code and run it, it works. "Hello_World" is shown as the heading.
if(1==1){shinyApp(ui, server)}
Now if I change that statement back, now that the global variable is already created by the previous run, and run it as an app, it works.
What I need to be able to get it to do is for the source functions to be able to read the global variables from the first run when the workspace is clear.
Up front: use source("...", local = TRUE).
source by default loads the file into the global environment, not the local environment. When you're working on your console, the two are the same. However, when you run the app, the app's environment is distinct, and not in the search-path for functions defined in the global environment.
Example:
main file:
global_var <- "Hello"
source("quux.R")
quux.R:
func <- function() {
return(global_var)
}
When sourced, I see
# Error in func() (from quux.R#2) : object 'global_var' not found
Namely,
environment(func)
# <environment: R_GlobalEnv>
When I change the main file to
global_var <- "Hello"
source("quux.R", local = TRUE)
then sourcing it works, as does func() by itself.
environment(func)
# <environment: 0x0000000054a995e0>
func()
# [1] "Hello"
Test Function.R does not have access to global_var because you are sourcing Test Function.R in your main file and not the other way around. (See answer by #r2evans using local = TRUE in source would solve the problem for you.)
Now to explain why it works when you have global_var in your workspace.
Consider this example -
#Remove everything from your global environment
rm(list = ls())
#define a function which returns d
fn <- function() return(d)
#Call the function
fn()
Error in fn() : object 'd' not found
#Now define d in global environment
d <- 12
fn()
#[1] 12 #This returns 12
So R by default looks for d in the functions environment, when it fails to find it it will go one level above which in this case is global environment and this time it gets the value of d so it returns that. The same thing happens in your case.

ShinyApp reactive error while trying to select variables from imported data set

I am very new to Shiny and R in general and I am building an app that allows users to import data, select their variables, number of trees.. ect and then run that through a random forest script and have it display the outputs. Right now I am just working on the inputs, however, I am running into a problem. The user can import a CSV but then they cannot select their variables (headers from csv). I am trying to make it reactive so the user first must import their csv before the option of selecting their variables pops up (seems simple).
Here is my code right now:
ui.R
server.R
Error in Console
I am probably just making a silly mistake because I am unfamiliar with Shiny but your help would be much appreciated.
A little background info
First off, I recommend you read this through to get a good understanding of reactivity in Shiny: Shiny Reactivity Overview. This helps a lot with variable scoping in a Shiny context too: Scoping Rules in Shiny Apps.
The issue
I believe this issue is due to you defining the variable file_to_read within the scope of shiny output: output$input_file in your server.R file. When the function read.table() looks for the variable file_to_read, it doesn't exist since it is only defined within the scope of the shiny output.
Try making a reactive value and then assigning the input$file value to it once it is uploaded by the user. You'll also have to convert your dat1 variable to a shiny reactive since you can only read reactive values in the context of other reactive sources (e.g. observe(), reactive(), observeEvent(), etc.)
file_to_read <- reactiveVal(NULL) # Set the value to NULL to initialize
output$input_file <- renderTable({
if (is.null(input$file)) return () # Check there is an input file
file_to_read(input$file) # Set the reactiveVal to the input file
})
# Make dat1 a reactive so it can be read within a shiny reactive
dat1 <- reactive({
if(is.null(file_to_read()) return (NULL) # Check for input file in reactiveVal
read.table(file_to_read()$datapath, sep = input$sep, header = input$header)
})
# Make an eventReactive to capture when there is read.table() data from dat1
reactive1 <- eventReactive(dat1, {
if (is.null(dat1)) return ()
D <- colnames(dat1)
return (list(D, D[1]))
})
I didn't test this code since you posted your data in image format and I don't have an input file, but hope this helps your error.

Reading an RData file into Shiny Application

I am working on a shiny app that will read a few RData files in and show tables with the contents. These files are generated by scripts that eventually turns the data into a data frame. They are then saved using the save() function.
Within the shiny application I have three files:
ui.R, server.R, and global.R
I want the files to be read on an interval so they are updated when the files are updated, thus I am using:
reactiveFileReader()
I have followed a few of the instructions I have found online, but I keep getting an error "Error: missing value where TRUE/FALSE is needed". I have tried to simplify this so I am not using:
reactiveFileReader()
functionality and simply loading the file in the server.R (also tried in the global.R file). Again, the
load()
statement is reading in a data frame. I had this working at one point by loading in the file, then assigning the file to a variable and doing an "as.data.table", but that shouldn't matter, this should read in a data frame format just fine. I think this is a scoping issue, but I am not sure. Any help? My code is at:
http://pastebin.com/V01Uw0se
Thanks so much!
Here is a possible solution inspired by this post http://www.r-bloggers.com/safe-loading-of-rdata-files/. The Rdata file is loaded into a new environment which ensures that it will not have unexpected side effect (overwriting existing variables etc). When you click the button, a new random data frame will be generated and then saved to a file. The reactiveFileReader then read the file into a new environment. Lastly we access the first item in the new environment (assuming that the Rdata file contains only one variable which is a data frame) and print it to a table.
library(shiny)
# This function, borrowed from http://www.r-bloggers.com/safe-loading-of-rdata-files/, load the Rdata into a new environment to avoid side effects
LoadToEnvironment <- function(RData, env=new.env()) {
load(RData, env)
return(env)
}
ui <- shinyUI(fluidPage(
titlePanel("Example"),
sidebarLayout(
sidebarPanel(
actionButton("generate", "Click to generate an Rdata file")
),
mainPanel(
tableOutput("table")
)
)
))
server <- shinyServer(function(input, output, session) {
# Click the button to generate a new random data frame and write to file
observeEvent(input$generate, {
sample_dataframe <- data.frame(a=runif(10), b=rnorm(10))
save(sample_dataframe, file="test.Rdata")
rm(sample_dataframe)
})
output$table <- renderTable({
# Use a reactiveFileReader to read the file on change, and load the content into a new environment
env <- reactiveFileReader(1000, session, "test.Rdata", LoadToEnvironment)
# Access the first item in the new environment, assuming that the Rdata contains only 1 item which is a data frame
env()[[names(env())[1]]]
})
})
shinyApp(ui = ui, server = server)
Ok - I figured out how to do what I need to. For my first issue, I wanted the look and feel of 'renderDataTable', but I wanted to pull in a data frame (renderDataTable / dataTableOutput does not allow this, it must be in a table format). In order to do this, I found a handy usage of ReportingTools (from Bioconductor) and how they do it. This allows you to use a data frame directly and still have the HTML table with the sorts, search, pagination, etc.. The info can be found here:
https://bioconductor.org/packages/release/bioc/html/ReportingTools.html
Now, for my second issue - updating the data and table regularly without restarting the app. This turned out to be simple, it just took me some time to figure it out, being new to Shiny. One thing to point out, to keep this example simple, I used renderTable rather than the solution above with the ReportingTools package. I just wanted to keep this example simple. The first thing I did was wrap all of my server.R code (within the shinyServer() function) in an observe({}). Then I used invalidateLater() to tell it to refresh every 5 seconds. Here is the code:
## server.R ##
library(shiny)
library(shinydashboard)
library(DT)
shinyServer(function(input, output, session) {
observe({
invalidateLater(5000,session)
output$PRI1LastPeriodTable <- renderTable({
prioirtyOneIncidentsLastPeriod <- updateILP()
})
})
})
Now, original for the renderTable() portion, I was just calling the object name of the loaded .Rdata file, but I wanted it to be read each time, so I created a function in my global.R file (this could have been in server.R) to load the file. That code is here:
updateILP <- function() {
load(file = "W:/Projects/R/Scripts/ITPOD/itpod/data/prioirtyOneIncidentsLastPeriod.RData", envir = .GlobalEnv)
return(prioirtyOneIncidentsLastPeriod)
}
That's it, nothing else goes in the global.R file. Your ui.R would be however you have it setup, call tableOutout, dataTableOutput, or whatever your rendering method is in the UI. So, what happens is every 5 seconds the renderTable() code is read every 5 seconds, which in turns invokes the function that actually reads the file. I tested this by making changes to the data file, and the shiny app updated without any interaction from me. Works like a charm.
If this is inelegant or is not efficient, please let me know if it can be improved, this was the most straight-forward way I could figure this out. Thanks to everyone for the help and comments!

shiny - reactive() to select between two reactive()

I'm making an app in Shiny which allows users to upload data or upload a save file.
If a user uploads new data then the flow looks like:
Upload Files >> Select Sheets >> Select Columns >> Sentence Tokenization >> Word Tokenization >> Graphics
Originally, I had been using observe() statements with reactiveValues() so at each step, I stored off the results into a reactiveValues(). This enabled the save file upload to work like:
Upload Save File >> Set sentences >> Set words >> Graphics
I would like to be able to accomplish something similar but using reactive() statements. So, given that I have uploadedFiles = reactive() and uploadedSave = reactive() how do I write a reactive with the following pseudo code:
rawText = reactive({
if uploadedFiles() flushes reactivity then uploadedFiles()
else if uploadedSave() flushes reactivity then uploadedSave()
else NULL
})
I don't have an "error" I'm trying to troubleshoot, just trying to think through the process of using a reactive to act like a 'most recently flushed' gate to allow my data flow to start from two different places and converge to a single one.
I'm going to show how I do it, but I have a feeling it is not a great method. I'm interested in hearing what the best method is and what's wrong with mine.
new_react <- reactiveValues()
new_react$FilesReact <- 0
new_react$SaveReact <- 0
invalidate <- function(x) x <- x+1
uploadedSave <- eventReactive(new_react$SaveReact,{
# Set sentences >> Set words >> Graphics
})
uploadedFiles <- eventReactive(new_react$FilesReact,{
# Select Sheets >> Select Columns >> Sentence Tokenization >> Word Tokenization >> Graphics
})
## I don't know how you are going to determine whether it's a data file
# or a save file...
if (its_a_save_file) {
new_react$SaveReact <- invalidate(new_react$SaveReact)
uploadedSave()
}else if (its_a_data_file) {
new_react$FilesReact <- invalidate(new_react$FilesReact)
uploadedFiles()
So basically I just define two reactive values which when invalidated by that simple function, will allow for the reactive functions, uploadedSave() or uploadedFiles() to be called.
Edit: To me, this just looks like a way to force eventReactive() to work like observeEvent()
Without more code, it is hard to be specific. Here is an app. that displays a different value based on what is the last control used. The controls are monitored using observeEvent() and each will put its input into a reactive value. Then the output just works with whatever is in the reactive value. For your case you need to have an observeEvent() for each of the controls you are using to do file and save uploads. Then they each put their file into a common reactiveValue and that value is used for further processing.
library(shiny)
app<-shinyApp(
ui=(fluidPage(sliderInput("First", min=1, max=10, label="Slider", value=1),
actionButton("Second", label="Second Button"),
textOutput("OutText"))
),
server=function(input,output){
RV<-reactiveValues(data=NULL)
observeEvent(input$First,
RV$Text<-input$First)
observeEvent(input$Second,
RV$Text<-"Second")
output$OutText<-renderText({
req(RV$Text)
RV$Text
})
}
)
runApp(app)

Loading and saving variables in R with gWidgets

For the past few months I've been building a simulation in R I hope to package. It consists of two usable functions, and many internal ones which one of the two usable functions call while looping, to perform the stages of simulation.
A simple conceptual example is:
# Abstract representation 1st Usable function, generates object containing settings for simulation.
settings <- function(){
matrix(c(1:4),nrow=2,ncol=2)
}
# Abstract representation of one of many internal functions, does some action in simulation.
int.func <- function(x){
out <- x*2
return(out)
}
# Abstract representation of second usable function, takes settings & invokes internal functions generates results & saves as R object files.
sim.func <- function(x){
ans <- int.func(x)
ans2 <- ans-2
save(ans2, file="outtest")
}
With my package so far, using it is done like so after loading and attaching the package with library():
INPUT <- settings()
fix(settings) # If you want to change from the defaults.
sim.func(INPUT)
Nothing needs returning from the simulation function because results and data-dumps get saved as an object with save() commands and the objects can be read in afterwards.
Now I'd like it to have a simple GUI which can be used in conjunction with command line - think Rcmdr but much more simple, to allow my co-workes who have never touched R to use it.
The gui needs to be able to edit the settings - like with the fix command above, to save a settings object to file, and to read in setting from an object file. I've built this with gWidgets:
gui <- function(){
INPUT <- matrix(c(1:4),nrow=2,ncol=2)
mainwin <- gwindow("MainWindow")
button1 <- gbutton("Edit Settings", cont=mainwin, handler=
function(h,...){
fix(INPUT)
print("Settings Edited")
})
button2 <- gbutton("RUN", cont=mainwin, handler=
function(h,...){
sim.func(INPUT)
print("The run is done")
})
savebutton <- gbutton("Write Settings to File",cont=mainwin, handler=
function(h,...){
setfilename <- ginput("Please enter the filename")
save(INPUT, file=setfilename)
})
loadutton <- gbutton("Load Settings from File", cont=mainwin,
handler=function(h,...){
fname <- gfile(test="Choose a file",
type="open",
action="print",
handler =
function(h,...){
do.call(h$action, list(h$file))
}
)
load(fname)})
}
Note the job of the settings function from before is now done on the first line of this gui function.
I add this to the same R file as the three functions above, add gui to the namespace as an export, set gWidgets and gWidgetstcltk as imports, and rebuild, then I library() the package and do gui().
The interface shows up. However I have a few issues:
The gui shows up fine, but if I click button1 ("Edit Settings") to edit settings through fix(INPUT), change the values, close the editor and click the button again to see if the changes have persisted and been stored in INPUT, they have not.
Same goes for reading in an object, it does not overwrite the INPUT object generated by default in the first line of function gui().
I think this has something to do with environments of functions but I'm not too sure. In the gui-less version of my package, the user generates the object containing settings, which is in workspace and feeds it to the simulation function as an argument. However since with the gui version, everything is run inside the function gui() and gWidgets handlers makes use of functions(h,...) I can't help but feel as if environments are the issue here. It's odd that when clicking on button 1, it will find INPUT from the gui() environment, but won't make the changes back there.
Can anybody help out with this and suggest what it is I need to do?
Apologies for a long question, but I've tried to explain clearly. Code is reproducible, as is the issue, just by having library(gWidgets, gWidgetstcltk)
and copying and pasting the code I've provided here, to define the functions and then running gui(). Then click the "Edit Settings" button, change the cells, exit, then click the button again to see if the changes persisted (they don't). The abstract example I've provided faithfully reproduces the same issues I have with my proper simulation functions so if I can't get it working, I won't get the real thing working.
Thanks,
Ben W.
UEA
The Sainsbury Laboratory.
[EDIT] Here is a fix/workaround using .GlobalEnv:
gui <- function(){
INPUT <- matrix(c(1:4),nrow=2,ncol=2)
.GlobalEnv$INPUT <- INPUT
mainwin <- gwindow("MainWindow")
button1 <- gbutton("Set", cont=mainwin, handler=
function(h,...){
INPUT <- .GlobalEnv$INPUT
fix(INPUT)
print("Settings have been edited...")
})
button2 <- gbutton("RUN", cont=mainwin, handler=
function(h,...){
sim.func(.GlobalEnv$INPUT)
print("The run is done")
})
writebutton <- gbutton("Write Settings to File",cont=mainwin, handler=
function(h,...){
setfilename <- ginput("Please enter the filename")
INPUT <- .GlobalEnv$INPUT
save(INPUT, file=setfilename)
})
loadutton <- gbutton("Load Settings from File", cont=mainwin,
handler=function(h,...){
fname <- gfile(test="Choose a file",
type="open",
action="print",
handler =
function(h,...){
do.call(h$action, list(h$file))
}
)
load(fname)
.GlobalEnv$INPUT <- INPUT})
}

Resources