In shiny, I am trying to have two observe events at once. One of them is a URL passed parameter. The other is that what I press a "Run" button, I output a csv.
The first part works fine and can be verified by adding /?param=some_text. However when I press the "Run" button no csv is created. What am I missing here? I feel like I have actionButton setup up correctly and that observeEvent is the right command to put to the csv?
library(shiny)
ui <- fluidPage(
textOutput("param"),
actionButton(inputId = "button", label="Run")
)
server <- function(input, output, session) {
observe({
query <- parseQueryString(session$clientData$url_search)
if (!is.null(query[['param']])) {
output$param <- renderText({
query[['param']]
})
} else {
output$param <- renderText({"unset"})
}
})
observeEvent(input$button, {
write.csv(iris, file = "temp.csv", row.names = TRUE)
})
}
shinyApp(ui, server)
I think the issue is with the filename which you're writing.
Listening on http://127.0.0.1:4380 Warning in file(file,
ifelse(append, "a", "w")) : cannot open file
'2022-11-25_10:32:31_temp.csv': Invalid argument Warning: Error in
file: cannot open the connection
You can use gsub to parse the filename so its compatible or change it to something else:
observeEvent(input$button, {
myfile <- paste0(Sys.time(),"_temp.csv")
myfile <- gsub(" ","_",myfile)
myfile <- gsub("-","_",myfile)
myfile <- gsub(":","",myfile)
write.csv(iris, file = myfile, row.names = TRUE)
})
Related
I have an excel file called testfile.xlsx. the first sheet of this file is called sheet1.
I have written appended a new sheet called New_Sheet using xlsx package as follows
library(xlsx)
setwd()##set the file path to where testfile.xlsx is located
write.xlsx('new_data', "testfile.xlsx", sheetName="New_Sheet", append=TRUE)
This adds the required sheet.
I have created the following shiny app to write the sheet to the file
library(shiny)
library(xlsx)
library(openxlsx)
library(readxl)
ui <- fluidPage(
titlePanel("Writer App"),
sidebarLayout(sidebarPanel(fileInput(inputId = "file", label = "Read File Here", accept =
c(".xlsx")),actionButton(inputId = "Run", label = "Write Data to Table")),
mainPanel(dataTableOutput(outputId = "table1"))))
server <- function(input, output) {
datasetInput <- reactive({
infile<- input$file
if (is.null(infile))
return(NULL)
#READ .XLSX AND .CSV FILES
if(grepl(infile, pattern = ".xlsx" )==T){
data=read_excel(infile$datapath)
} else if(grepl(infile , pattern = ".csv" )==T)
{data=read.csv(infile$datapath )}
#RENAME DATAFRAME WITH UNDERSCORES
names(data)<-gsub(pattern = " ", replacement = "_", x = names(data))
return(data) })
output$table1 <- renderDataTable({
datasetInput()})
observeEvent(input$Run,{
infile<-input$file
testfile<-(infile[1])
filepath<-(input$file)
filepath<-gsub(pattern = "0.xlsx", replacement ="" , x = filepath)
# print(infile$datapath[1])
print(filepath)
print(testfile)
setwd(dir = filepath)
write.xlsx('new_data', testfile, sheetName="New_Sheet3", append=TRUE)})
}
shinyApp(ui = ui, server = server)
The app renders the data in the excel sheet as a table without any problems
.When we push the run app button, the print commands generate the name of the file and the filepath. The write excel function doesnt work. Is there a way to insert the new_data sheet using the action button. I request someone to guide me here.
I recommend using downloadHandler instead. See here for an example.
I have a function (clawCheck) defined in the file CheckClawback.R which takes three data frames as arguments. In my Shiny app, the user uploads three files which are then read into memory and used as the ClawCheck arguments. In order to save time, I want R to start reading a file into memory as soon as it is uploaded, and not only after the "GO" button is pressed, so that once the button is pressed, the arguments for ClawCheck are already in memory and ready to use.
I'm thinking that I have to use eventReactive expressions within the renderTable statement, since I don't want the files to be re-read every time a user changes some input. To avoid further complication, I assume the input is filled in in order, i.e first "account", then "commpaid", then "termriders". When I run the app and the first input file has been uploaded, there is no progress bar appearing which indicates that my code is not working correctly. Here is my (reduced) code:
library('shiny')
source("CheckClawback.R")
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("account", "Account File (.csv)"),
fileInput("commpaid", "CommPaid File (.txt)"),
fileInput("termriders", "TermRiders File (.txt)"),
actionButton("do", "GO!")),
mainPanel(
tableOutput("out_table"))
)
)
server <- function(input, output) {
func <- eventReactive(input$do, {
req(acc)
req(comm)
req(term)
datat <<- clawCheck(acc, comm, term)
})
output$out_table <- renderTable({
eventReactive(input$account, {
withProgress(message = "Preparing Account Data...Please Wait", {
acc <<- read.csv(input$account$datapath, header = TRUE, sep = ",")
})
})
eventReactive(input$commpaid, {
withProgress(message = "Preparing CommPaid Data...Please Wait", {
comm <<- read.table(input$commpaid$datapath, header = TRUE, sep = "\t")
})
})
eventReactive(input$termriders, {
withProgress(message = "Preparing TermRiders Data...Please Wait", {
term <<- read.table(input$termriders$datapath, header = TRUE, sep = "\t")
})
})
withProgress(func(), message = "Loading Output...Please Wait")
datat
})
}
shinyApp(ui = ui, server = server)
Ideally, after a file is uploaded, a progress bar should appear, indicating that it is being processed. If, during this process, a second file is uploaded, a second progress bar should appear, indicating that the second file is being processed etc. Once the actual function call happens, I want the input files to be ready to go.
I am very thankful for any help!
Your use of <<- and withProgress() is wrong. Also, using eventReactive() inside a render*() is wrong. I suggest going through RStudio Shiny tutorials to get help on understanding how reactivity works. Also look at showNotification() instead of withProgress(). For now, here's what you probably need -
server <- function(input, output, session) {
acc <- reactive({
validate(need(input$account), "acc not uploaded")
# use showNotification(); use same approach for other files
read.csv(input$account$datapath, header = TRUE, sep = ",")
# use removeNotification() to remove mesg after file is uploaded
})
comm <- reactive({
validate(need(input$commpaid), "comm not uploaded")
read.table(input$commpaid$datapath, header = TRUE, sep = "\t")
})
term <- reactive({
validate(need(input$termriders), "term not uploaded")
read.table(input$termriders$datapath, header = TRUE, sep = "\t")
})
func <- eventReactive(input$do, {
clawCheck(acc(), comm(), term())
})
output$out_table <- renderTable({
func()
})
}
So I want to have a Shiny page which:
A) Allows the user to upload a .xls file;
B) Offers that file back to the user for download as a .csv file;
C) Prints the head of the file in the Shiny app to ensure that it was properly read.
Here is the code I am using:
# Want to read xls files with readxl package
library(readxl)
library(shiny)
## Only run examples in interactive R sessions
if (interactive()) {
ui <- fluidPage(
fileInput("file1", "Choose File", accept = ".xls"),
tags$hr(),
uiOutput("downloader"),
htmlOutput("confirmText", container = tags$h3),
tableOutput("listContents")
)
server <- function(input, output) {
theOutput <- reactiveValues(temp = NULL, df = NULL, msg = NULL, fn = NULL)
observeEvent(input$file1, {
theOutput$fn <- paste('data-', Sys.Date(), '.csv', sep='')
theOutput$temp <- read_xls(input$file1$datapath)
theOutput$msg <- paste("File Contents:")
theOutput$df <- write.csv(theOutput$temp,
file = theOutput$fn,
row.names = FALSE)
})
output$confirmText <- renderText({
theOutput$msg
})
output$listContents <- renderTable({
head(theOutput$temp)
})
output$downloader <- renderUI({
if(!is.null(input$file1)) {
downloadButton("theDownload", label = "Download")
}
})
output$theDownload <- downloadHandler(
filename = theOutput$fn,
content = theOutput$df
)
}
shinyApp(ui, server)
}
The Shiny page renders correctly, it accepts the upload with no problems, it prints out the head of the .csv with no problems, and it creates a properly formatted "data-{today's date}.csv" file in the same directory as the app.R file.
Problem is, when I hit the download button I get the error message:
Warning: Error in download$func: attempt to apply non-function
[No stack trace available]
Can someone tell me what I am doing wrong?
Thanks to the comments above, this is the solution I found (with my comments added, to show where the code changed):
library(readxl)
library(shiny)
if (interactive()) {
ui <- fluidPage(
fileInput("file1", "Choose File", accept = ".xls"),
tags$hr(),
uiOutput("downloader"),
htmlOutput("confirmText", container = tags$h3),
tableOutput("listContents")
)
server <- function(input, output) {
theOutput <- reactiveValues(temp = NULL, msg = NULL)
observeEvent(input$file1, {
# Do not try to automate filename and the write.csv output here!
theOutput$temp <- read_xls(input$file1$datapath)
theOutput$msg <- paste("File Contents:")
})
output$confirmText <- renderText({
theOutput$msg
})
output$listContents <- renderTable({
head(theOutput$temp)
})
output$downloader <- renderUI({
if(!is.null(input$file1)) {
downloadButton("theDownload", label = "Download")
}
})
output$theDownload <- downloadHandler(
# Filename and content need to be defined as functions
# (even if, as with filename here, there are no inputs to those functions)
filename = function() {paste('data-', Sys.Date(), '.csv', sep='')},
content = function(theFile) {write.csv(theOutput$temp, theFile, row.names = FALSE)}
) }
shinyApp(ui, server) }
The fact that content takes an argument (named here "theFile"), which is not called anywhere else, is what was throwing me off.
I have simplified a lot the shiny app I'm trying to build, but, in the idea, I have two functions :
choose_input <- function(n1,n2,n3){
x1 <<- n1+n2
x2 <<- n2+n3
x3 <<- (n1*n2)/n3
}
createmydata <- function(n){
c1 <- c(1:n)
c2 <- c1+(x2*x3)
c3 <- c2+x1
df <- data.frame("column1"=c1,"column2"=c2,"column3"=c3)
return(df)
}
You'll tell me that I can do simply one function with these two because they are very simple, but in my app there are a lot of lines and I have to separate the two. Anyway, here is my simulated code :
ui <- fluidPage(
numericInput("n1",label="Choose the first parameter",min=0,max=100,value=3),
numericInput("n2",label="Choose the second parameter",min=0,max=100,value=4),
numericInput("n3",label="Choose the third parameter",min=0,max=100,value=5),
numericInput("n",label="Choose dataframe length",min=1,max=10000,value=100),
radioButtons("filetype", "File type:",
choices = c("csv", "tsv")),
downloadButton('downloadData', 'Download'),
tableOutput("data")
)
server <- function(input,output){
RE <- reactive({
choose_input(input$n1,input$n2,input$n3)
createmydata(input$n)
})
output$data <- renderTable({
RE()
})
output$downloadData <- downloadHandler(
filename = function() {
paste(name, input$filetype, sep = ".")
},
content = function(file) {
sep <- switch(input$filetype, "csv" = ",", "tsv" = "\t")
write.table(RE(), file, sep = sep,
row.names = FALSE)
}
)
}
shinyApp(ui = ui, server=server)
As you can see, I'd like to download the output table to a csv or excel file... I let you try the code and then try to click on the download button, it does not work...
Debugging
When I run the code up above and attempted to download the data set, I received the following warning and error message in the Console Pane within RStudio.
Warning: Error in paste: object 'name' not found
Stack trace (innermost first):
1: runApp
Error : object 'name' not found
This led me to examine the paste() function used within the filename argument in shiny::downloadHandler(). In your code, you use the object name without ever assigning it a value.
I replaced name with the text "customTable" within the filename argument inside of downloadHandler().
output$downloadData <- downloadHandler(
filename = function() {
paste( "customTable", input$filetype, sep = ".")
},
content = function(file) {
sep <- switch(input$filetype, "csv" = ",", "tsv" = "\t")
write.table(RE(), file, sep = sep,
row.names = FALSE)
}
)
Downloading Data in Browser
After running the app.R script, I clicked on the Open in Browser button to view the Shiny app in a new tab on Chrome. Once there, I was successfully able to download both a .csv and .tsv file after hitting the download button.
Note: I'm looking for a better reason as to why this action needs to occur, but for now, I came across this relevant SO post Shiny app: downloadHandler does not produce 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)