Running scripts on uploaded csv file in shiny - r

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)
}
)
})
))

Related

Shiny app file upload is substantially slower on different machines

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)

How to restrict, importing file to shinyApp once per day in R shiny?

I want to upload updated csv file daily basis. Once the csv file get uploaded, the upload icon should disappear and valueBox should display with relevant value. Here is the below code:
library(shiny)
library(shinydashboard)
# Define UI for application that draws a histogram
ui <- dashboardPage(
dashboardHeader(title = "Upload Stats"),
dashboardSidebar(),
dashboardBody(
box(
title = "UPTIME:", width = 12,
div(column(width = 4, fileInput(inputId = "file", label = "import", accept = ".csv")),
column(width = 8, valueBoxOutput("stats"))
)
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$stats <- renderValueBox({
req(input$file)
data <- read.csv(input$file$datapath)
valueBox("scr1", sum(data[,2]), width = 12)
})
}
# Run the application
shinyApp(ui = ui, server = server)
The above code accepting csv file each time visiting the shinydashboard. Currently it showing the upload icon each time someone opens the URL/dashboard. I want the upload icon should shown till csv file not uploaded into shinyApp. Once uploaded, it should disappear and should display 'valueBox()' with values depend on the uploaded file.
Can someone help me how to write that control code?
Since your application is going to be used by multiple people who can access the URL, the simple way would be to create a global .rds file accessible by all the users whenever the .csv file is imported.
data <- read.csv(input$file$datapath)
# Create a folder named srcdata under www folder in your app directory
# Save the data object as a .rds file with system date appended to the file name
saveRDS(data,paste0("www/srcdata/data_",Sys.Date()))
However, we would need to create this .rds file only once per day. If a file already exists for the current date, we can
1. Skip this step and read the file directly
2. Hide the input field from the UI
So the code becomes
filePresent <- list.files("www/srcdata/", pattern = paste0("data_",Sys.Date()))
# if file is present, disable the input field and read from the saved .rds
# if file is not present, show the input field
if(length(filePresent)==1){
data <- readRDS(paste0("www/srcdata/data_",Sys.Date()))
filedata$notPresent <- FALSE
}else{
shinyjs::show("file")
}
Here, we are using shinyjs to show and hide the fields. So you would need to install that package (if not already) and call it in your code. Also, this code should run every time the app gets initialized so that the users either get presented with data (if there is a saved file) or sees a input field to import the file.
I have updated the code to implement this
library(shiny)
library(shinydashboard)
library(shinyjs)
# Define UI for application that draws a histogram
ui <- dashboardPage(
dashboardHeader(title = "Upload Stats"),
dashboardSidebar(),
dashboardBody(
useShinyjs(),
box(
title = "UPTIME:", width = 12,
div(column(width = 4, hidden(fileInput(inputId = "file", label = "import", accept = ".csv"))),
column(width = 8, valueBoxOutput("stats"))
)
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
filedata <- reactiveValues(notPresent = TRUE)
observeEvent(filedata$notPresent,{
if(filedata$notPresent){
filePresent <- list.files("www/srcdata/", pattern = paste0("data_",Sys.Date()))
if(length(filePresent)==1){
data <- readRDS(paste0("www/srcdata/data_",Sys.Date()))
filedata$notPresent <- FALSE
}else{
shinyjs::show("file")
}
}
})
output$stats <- renderValueBox({
req(input$file)
data <- read.csv(input$file$datapath)
saveRDS(data,paste0("www/srcdata/data_",Sys.Date()))
valueBox("scr1", sum(data[,2]), width = 12)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Hope this helps!

Add Select columns dynamically in R with ends_with in Shiny App

Using stackoverflow, I created a shiny app which uploads a csv file and then displays a datatable.
After selecting columns dynamically, where some columns have "_down" end.
I require help in shortening the dataframe (as in the code below) and also remove duplicates by ID column (if present).
# install.packages("shiny")
# install.packages("DT")
# install.packages("shinycssloaders")
library(DT)
library(shiny)
library(shinycssloaders)
UI code
##Creating the UI as a fluidPage,
##fluidPage allows scaling components of the browser in realtime to fill all available broswer width
##This is standard
ui <- fluidPage(
# Title of app
titlePanel("Upload file to table"),
# Main panel for displaying outputs
mainPanel(
#fileInput with acceptance of text/csv and more
fileInput('file', 'Choose file to upload',
accept = c(
'text/csv',
'text/comma-separated-values',
'text/tab-separated-values',
'text/plain',
'.csv',
'.tsv',
'.html'
)),
# Output: datatable
DT::dataTableOutput("data_as_table")%>%withSpinner(),
#Download button
downloadButton("downloadData", "Download")
)
)
Server Code
Creating server
server <- function(input, output) {
#Data is a reactive element meaning it will update when the reactive input inside it change
#Data will update when input$file changes
#input$file is the uploaded file (se fileInput in ui)
data <-reactive({
#Store input$file as inFile
inFile <- input$file
#if its empty return nothing
if (is.null(inFile))
return(NULL)
#read in the file as a csv, with headers, comma seperated
dd = read.csv(inFile$datapath, header = T,
sep = ",")
dd = as.data.frame(dd)
#Shortening dataframe
#dd= dd[apply(dd[, endsWith(colnames(dd), "_down")], 1, function(x) any(x == "TRUE")), ]
#Remove duplicates by ID column, and show unique
#xxx
return(dd)
})
#Make the output data_as_table a datatable containing the reactive element data
output$data_as_table<-DT::renderDataTable({
data()
})
# Downloadable csv of reactive data() object
output$downloadData <- downloadHandler(
filename = function() {
paste("Download", Sys.date(), ".csv", sep = "")
},
content = function(file) {
write.csv(data(), file, row.names = FALSE)
}
)
}
#Launch shiny app
shinyApp(ui = ui, server = server)
You can remove duplicates using dplyr::distinct. It'll only keep the first instance of the ID and remove others. In your case add this before return(dd) in data reactive -
if("ID" %in% names(dd)) {
dd <- dplyr::distinct(dd, ID, .keep_all = T)
}

Download table generated independetly in renderTable output in R shiny

I am trying to generate a table using the renderTable function in R shiny and then use the downloadHandler function to download that table/data.frame as a csv file. Somehow I keep getting the following error:
An error occured during download:
Error downloading http://127:0:0.1:3001/session/
0303bd426fce88837ae277aa3b406dd/download/downloadData?w= - server
replied: Internal Server Error
Below is an example code where I generate a simple data frame and try to download it using downloadHander:
library(shiny)
# Define UI for data download app ----
ui <- fluidPage(
# App title ----
titlePanel("Downloading Data"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
# Button
downloadButton("downloadData", "Download")
),
# Main panel for displaying outputs ----
mainPanel(
tableOutput("table")
)
)
)
# Define server logic to display and download selected file ----
server <- function(input, output) {
# Table of selected dataset ----
output$table <- renderTable({
data.frame(a =c(1,2,3),b=c("q","s","f"))
})
# Downloadable csv of selected dataset ----
output$downloadData <- downloadHandler(
filename = function() {
paste("test.csv")
},
content = function(file) {
write.csv(output$table, file, row.names = FALSE)
}
)
}
shinyApp(ui,server)
There are a few things that need to be done here:
If your app is going to render data dynamically, then your data should be assigned to some reactive expression.
Now the downloading of the data becomes easy, as you just call the reactive expression written in (1).
Points (1) and (2) above will ensure that the user is downloading the same data that is seen on the screen.
Try the following:
library(shiny)
ui <- fluidPage(
titlePanel("Downloading Data"),
sidebarLayout(
sidebarPanel(downloadButton("downloadData", "Download")),
mainPanel(tableOutput("table"))
)
)
server <- function(input, output) {
data <- shiny::reactive(data.frame(a = c(1, 2, 3), b = c("q", "s", "f")))
output$table <- renderTable(data())
output$downloadData <- downloadHandler(
filename = function() {
paste("test.csv")
},
content = function(file) {
write.csv(data(), file, row.names = FALSE)
}
)
}
shinyApp(ui,server)
You cannot export a renderTable{} as this puts many of the elements into HTML,
you need to previously save the data going into the table and export it
seperately.
dataTable<-data.frame(a =c(1,2,3),b=c("q","s","f"))
output$downloadData <- downloadHandler(
filename = function() {
('test.csv')
},
content = function(con) {
write.table(dataTable,row.names = FALSE,col.names=T, sep=",",con)
},
contentType="csv"
)

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)

Resources