Shiny app file upload is substantially slower on different machines - r

I have a shiny application that takes a file upload, calls a script that processes the uploaded file, and writes 4 csvs as output. The app works but as the title suggests, the file upload takes ~5 seconds on my end, but the intended end user is waiting 40 minutes for the same 32 MB file to upload. How do I reduce this upload time for them?
I am attaching my code, but here are some additional points that may be relevant:
The shiny code, the script it calls, and the file to be uploaded are all on a shared drive.
I am accessing their system through a virtual desktop, while the end user has a company computer.
Thanks in advance.
library(shiny)
source([removed for confidentiality])
# Define UI for dataset viewer app ----
ui <- fluidPage(
# App title ----
titlePanel("DFM File Conversion"),
# Sidebar layout with a input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Input: Selector for choosing dataset ----
textInput(inputId = "exportname1",
label = "Credit Detail [003 Record] Output Name",
value = ""),
textInput(inputId = "exportname2",
label = "Location Bank Deposit [013 Record] Output Name",
value = ""),
textInput(inputId = "exportname3",
label = "Batch Summary [025 Record] Output Name",
value = ""),
textInput(inputId = "exportname4",
label = "Rejected Transactions [029 Record] Output Name",
value = ""),
fileInput("file1", "Please upload a file")
),
# Main panel for displaying outputs ----
mainPanel(
verbatimTextOutput("summary") #shows what files were converted
,h3(textOutput("caption"))
,tableOutput("view") # shows which records are not present in uploaded file
,h3(textOutput("caption2"))
,tableOutput("headdf") #shows first 5 rows of uploaded file
)
)
)
server <- function(input, output) {
options(shiny.maxRequestSize=60*1024^2)
# This reads in the uploaded file from the UI and outputs the first 5 rows
# Then it uses the export name entered by the user to convert the file
# using the conversion script.
output$view <- renderTable({
req(input$file1)
df <- read.delim(input$file1$datapath,header = FALSE, stringsAsFactors = FALSE)
converted <- convertdfm(df, input$exportname1, input$exportname2, input$exportname3, input$exportname4)
# this populates which records are not present in uploaded data
return(converted$output)
})
# this prints the first 4 rows of the file
output$headdf <- renderTable({
req(input$file1)
df1 <- read.delim(input$file1$datapath,header = FALSE, stringsAsFactors = FALSE)
head(df1)})
# this creates the first caption
output$caption <- renderText({
req(input$file1)
print("Checking Input Files for Unavailable Records")
})
# this creates the second caption
output$caption2 <- renderText({
req(input$file1)
print("First 5 Rows of Raw Data")
})
# this shows what files were converted
output$summary <- renderPrint({
req(input$file1)
if (file.exists(input$exportname1))
{print("003 Converted")} else
{print("003 Not Converted")}
if (file.exists(input$exportname2))
{print("013 Converted")} else
{print("013 Not Converted")}
if (file.exists(input$exportname3))
{print("025 Converted")} else
{print("025 Not Converted")}
if (file.exists(input$exportname4))
{print("029 Converted")} else
{print("029 Not Converted")}
}
)
}
# Create Shiny app ----
shinyApp(ui = ui, server = server)

Related

R Shiny: 'file' must be a character string or connection when using read.csv()

I'm having an issue which I thought would have been very simple to solve, but I cannot figure it out.
I simply want to pass an uploaded csv file to a custom function in Shiny and output the result which is a ggplot graph. Here is my code for doing so
# Getting the file names
rdsfiles <- list.files(pattern = "\\.Rds$")
# --- Front End ---
ui <- shinyUI(fluidPage(theme = shinytheme("cerulean"), pageWithSidebar(
# Title
headerPanel("Title"),
# Sidebar to select a dataset
sidebarPanel(
selectInput("obj", "Choose a dataset:",
choices = rdsfiles),
fileInput("tissue_csv",
"Load tissue positions .csv file",
accept = c("text/csv", "text/comma-separated-values,text/plain",".csv")
),
textInput("feature", label = "Gene"),
),
# Different analyses available
mainPanel(
tabsetPanel(
tabPanel('UMAP', plotOutput("umap")),
tabPanel('Tissue', plotOutput("tissue")),
tabPanel('Gene Expression', plotOutput("genex")),
))
)))
# --- Back end ---
server <- shinyServer(function(input, output) {
# Return the requested datasets
datasetInput <- reactive({
df <- readRDS(input$obj, input$obj)
return(df)
})
tissueInput <- reactive({
inFile <- req(input$tissue_csv)
read.csv(inFile$datapath)
})
### HERE IS WHERE THE ERROR LIES ###
output$tissue <- renderPlot({
obj <- datasetInput()
tiss <- tissueInput()
custom_function(obj, tiss)
})
# Retrieve the UMAP projection
output$umap <- renderPlot({
obj <- datasetInput()
DimPlot(obj, reduction = "umap")
})
})
shinyApp(ui, server)
Whenever I use the app and upload my .csv file, it always gives me an error message that says 'file' must be a character string or connection. Why is this? Any suggestions?

Server permissions for shiny downloadhandeller()

I am trying to setup a shiny app that can download html plots from the googleViz package. The code works on my machine, but when I move it to the server I get the following message when testing the download...
"The requested URL was rejected. Please consult with your administrator."
I am struggling to figure out what the IT staff, that set up the server, need to do to fix the problem - I know nothing about servers and they know nothing about R.
I built a small example app here to demonstrate the problem, based on the following ui.R
library(shiny)
library(googleVis)
# user interface
shinyUI(pageWithSidebar(
headerPanel("googleVis on Shiny"),
sidebarPanel(
selectInput("dataset", label = "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
downloadButton('download_gvis', label = 'Download')
),
mainPanel(
htmlOutput("view")
)
))
and server.R
library(googleVis)
library(webshot)
shinyServer(function(input, output) {
# data set from user
datasetInput <- reactive({
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# plot of data set from user
my_plot <- reactive({
gvisScatterChart(datasetInput(),
options=list(title=paste('Data:',input$dataset)))
})
# render plot of data set from user
output$view <- renderGvis({
my_plot()
})
# download plot of data set from user
output$download_gvis <- downloadHandler(
filename = "test.png",
content = function(file) {
g <- my_plot()
# print to html file
print(g, file = "gg.html")
# take a webshot of html file and save as png
webshot(
url = "gg.html",
file = "output.png",
delay = 2
)
# send output file to downloadHandler
file.copy("output.png", file)
# delete files
file.remove("gg.html")
file.remove("output.png")
}
)
})
I think the code breaks at print(g, file = "gg.html") in the server script. Thegg.html file never appears in the server directory (on my local machine I see it pop up in the directory view of RStudio).

r shiny: How to print a message in the app after the user forgets to upload a file?

I am building a rudimentary shiny app.
First, I created a data frame 'x' and saved it in my working directory:
x <- data.frame(a = 1:4, b = 2:5)
write.csv(x, 'x.csv', row.names = F)
In my shiny I'd like to:
Upload file 'x.csv'
Click my action button 'Click Here' and run a few commands upon clicking it.
Get a message printed in the Shiny app itself: "Load a file!" if I click on my button "Click here" after forgetting to upload the file first.
My code works, but I can't figure out how to make my message appear.
My code:
library(shiny)
ui <- fluidPage(
br(),
# User should upload file x here:
fileInput("file_x", label = h5("Upload file 'x'!")),
br(),
# Users clicks the button:
actionButton("do_it", "Click Here"),
br(),
# Print last value of the button 'do_it':
verbatimTextOutput("print_action")
)
server <- function(input, output, session) {
observeEvent(input$do_it, {
# Just a check of my button's actions:
output$print_action <- renderPrint({input$do_it})
# Validating the input - next 5 lines are not working:
# validate(
# need(
# try(is.null(input$file_x), "Load a file!")
# )
# )
# Reading in the file:
fileData <- reactive({
infile <- input$file_x
if (is.null(infile)) {
return(NULL)
}
read.csv(infile$datapath)
})
x <- fileData()
# Writing out the same file - but under a different name:
filename <- paste0("x", input$do_it, ".csv")
write.csv(x, file = filename, row.names = FALSE)
})
}
shinyApp(ui, server)
I think rather than displaying text, maybe modalDialog is better suited for what you are trying to achieve. I have implemented both solutions below, so you can compare.
Note that I also modified the reading of the csv slightly. It is bad practice to set a reactive from inside an observer. In those cases, it is better to use a reactiveVal, and update that from an observer.
Hope this helps!
library(shiny)
ui <- fluidPage(
br(),
# User should upload file x here:
fileInput("file_x", label = h5("Upload file 'x'!")),
br(),
# Users clicks the button:
actionButton("do_it", "Click Here"),
br(),
br(),
# Print last value of the button 'do_it':
verbatimTextOutput("print_action")
)
server <- function(input, output, session) {
observeEvent(input$do_it, {
if(is.null(input$file_x))
{
# show pop-up ...
showModal(modalDialog(
title = "Oh no!",
paste0("You have not uploaded a file, silly person!"),
easyClose = TRUE,
footer = NULL
))
# ... or update the text
my_text('Please upload a file.')
}
else
{
# Reading in the file:
infile <- input$file_x
if (is.null(infile)) {
return(NULL)
}
x <- read.csv(infile$datapath)
fileData(x) # set the reactiveVal called fileData to the file inputs.
# Writing out the same file - but under a different name:
filename <- paste0("x", input$do_it, ".csv")
write.csv(x, file = filename, row.names = FALSE)
my_text('Succes!')
}
})
fileData <- reactiveVal()
my_text <- reactiveVal('')
output$print_action <- renderText({my_text()})
}
shinyApp(ui, server)

Running scripts on uploaded csv file in shiny

I'm trying to build an application that takes a csv file from the user, uploads it, then the user fill some text boxes that will fill specific columns in the data frame later on, clicks a button 'GO', some scripts run in the background and we have a data frame ready for download. The thing is the whole reactive architecture makes it difficult to set up a step by step algorithm. Could you help me with setting up the framework for doing that? Ideally it would look like follows
shinyUI(fluidPage(
titlePanel("Uploading Files"),
fileInput('file1', 'Choose file to upload',
accept = c('text/csv',
'text/comma-separated-values',
'text/tab-separated-values',
'text/plain','.csv','.tsv')),
dateInput('date',"Select when the file was uploaded",
value = NULL,
format = 'yyyy-mm-dd'),
textInput('text1','Type what will be in column 6'),
textInput('text2','Type what will be in column 7'),
actionButton('go','go'),
tableOutput('readytable')
And now having that front i would like to: 1. Load the dataframe from csv of the user 2. Wait for the user to fill other input boxes 3. After clicking 'go' run bunch of functions on the data frame with the inputs that the user have inserted as for example df$column6 <- input$text1 and after that i'm left with a data frame that is ready to be written as a csv file once again. Thanks in advance for any links/suggestions
You can use reactive variables to control reactivity on shiny. Here is an example for your problem. Please note that the download button doesn't works on the RStudio viewer, so launch the app in a browser if you want to use the download button.
library(shiny)
runApp(list(
ui = shinyUI(pageWithSidebar(
headerPanel('Uploading Files'),
sidebarPanel(
fileInput('file1', 'Choose file to upload',
accept = c('text/csv',
'text/comma-separated-values',
'text/tab-separated-values',
'text/plain','.csv','.tsv')),
uiOutput('buttonsUI'), br(),
uiOutput('downloadUI')
),
mainPanel(
tableOutput('readytable')
)
)),
server = shinyServer(function(input, output) {
# variables to control the sequence of processes
controlVar <- reactiveValues(fileReady = FALSE, tableReady = FALSE)
# to keep the data upload
dat <- NULL
# handle the file reading
observeEvent(input$file1, {
controlVar$fileReady <- FALSE
if (is.null(input$file1))
return()
inFile <- input$file1
dat <<- read.csv(inFile$datapath)
if(!is.data.frame(dat))
return()
controlVar$fileReady <- TRUE
})
# show buttons only when file is uploaded
output$buttonsUI <- renderUI({
if (controlVar$fileReady)
div(
dateInput('date','Select when the file was uploaded',
value = NULL,
format = 'yyyy-mm-dd'),
textInput('text1','Type what will be in column 6'),
textInput('text2','Type what will be in column 7'),
actionButton('go','go')
)
})
# show a download button only if data is ready
output$downloadUI <- renderUI({
if (controlVar$tableReady)
downloadButton('downloadData', 'Download')
})
# add columns to dat and run some script on it
observeEvent(input$go, {
controlVar$tableReady <- FALSE
if (!is.null(input$text1))
dat$column6 <<- input$text1
if (!is.null(input$text2))
dat$column7 <<- input$text2
# simulate running a cool script on dat
Sys.sleep(2)
controlVar$tableReady <- TRUE
})
# render table after uploading file or running the script
output$readytable <- renderTable({
input$go
if (controlVar$fileReady || controlVar$tableReady)
dat
})
# handle the download button
output$downloadData <- downloadHandler(
filename = function() { 'newData.csv' },
content = function(file) {
write.csv(dat, file)
}
)
})
))

R Shiny request user to choose directory

I am new to R and R Shiny.
For the code i have at the moment i need to manually input the file name, i would like to generalize the case and let the user to pick working directory and corresponding file name.
1, user choose working directory
then shiny able to store all the file names under the selected working directory. similar to list.files()
2, then the box list files will list all file names under the selected wd
and user able to check which dataset should be shown
3, in the mainpanel
top 10 instances of the dataset with the header will be shown
What i have tried is
server.R
library(shiny)
setwd("C:/Users/HKGGAIT001/Google Drive/GA Project/Cargo/Cargo.Statistics/data/Hactl")
data1 <- read.csv(list.files()[1])
data2 <- read.csv(list.files()[2])
# Define server logic required to summarize and view the selected
# dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive({
switch(input$dataset,
"data1" = data1,
"data2" = data2)
})
# Generate a summary of the dataset
output$summary <- renderPrint({
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- renderTable({
head(datasetInput(), n = input$obs)
})
})
ui.R
library(shiny)
# Define UI for dataset viewer application
shinyUI(fluidPage(
# Application title
titlePanel("Shiny Text"),
# Sidebar with controls to select a dataset and specify the
# number of observations to view
sidebarLayout(
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("data1", "data2")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show a summary of the dataset and an HTML table with the
# requested number of observations
mainPanel(
verbatimTextOutput("summary"),
tableOutput("view")
)
)
))
The situation is similar to This website while my case is request user to pick local working directory.
Thanks for your gentle help
First, create the .csv files to reproducibility:
write.csv(x = data.frame(V1 = 1:13, V2 = letters[1:13]),
file = "teste1.csv", row.names = FALSE)
write.csv(x = data.frame(V1 = 14:26, V2 = letters[14:26]),
file = "teste2.csv", row.names = FALSE)
write.csv(x = data.frame(V1 = rnorm(15), V2 = runif(15)),
file = "teste3.csv", row.names = FALSE)
Add a global.R script in your app might be useful. In this script you would be able to:
i. let the user select the working directory,
ii. read the .csv files in that folder,
iii. create a list of files that could be used by ui.R and server.R
# global.R
library(shiny)
wd <<- choose.dir()
setwd(wd)
csv <<- list.files(pattern = ".csv")
files <<- vector("list", length(csv))
for (i in seq_along(files)) {
files[[i]] <- read.csv(csv[i], stringsAsFactors = FALSE)
}
list_of_datasets <<- seq_along(files)
names(list_of_datasets) <- gsub(pattern = ".csv", replacement = "", x = csv)
Then you just have to make a few changes in the original scripts you provided us. In ui.R I would redefine the selectInput function so that displays the name of the files to the users. Also, you can't be sure that the selected folder would have 2 files.
selectInput("dataset", "Choose a dataset:",
choices = list_of_datasets)
In server.R you should i) remove the 2nd, 3rd and 4th lines (already handled by global.R) and ii) change datasetInput function:
datasetInput <- reactive({
files[[as.numeric(input$dataset)]]
})

Resources