I am developing an Rshiny application in which I edited the contents of the dataframe and downloading the edited dataframe in .csv format. But the downloaded file is not in .csv format. Can anyone help me with this issue?
output$downloadData <- downloadHandler(filename = function() {paste(Sys.time(), 'Edited Table.csv', sep='') } ,content = function(file) {write.csv(sample_data(), file, row.names = FALSE)})
This is the code used. Thanks in advance!!
I've tried to replicate your results with an example, but I couldn't reproduce your issue. Here is a self-sufficient RShiny App which downloads a .csv file. Make sure your app follows this template, if the issue still persists please provide a reproducible example of the same.
library(shiny)
ui <- fluidPage(
downloadButton("downloadData", "Download")
)
server <- function(input, output) {
# Your Sample dataset
sample_data <- reactive({mtcars})
output$downloadData <- downloadHandler(
filename = function() {
paste(Sys.time(), ' Edited Table.csv', sep='')
},
content = function(file) {
write.csv(sample_data(), file, row.names = FALSE)
}
)
}
shinyApp(ui, server)
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.
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 a shiny app which takes a large amount of time downloading zip files. I am trying to use the futures and promises packages to manage the downloads so that other users can access the app while downloads are in progress.
The app looks as below:
library(shiny)
ui <- fluidPage(
downloadButton("Download", "Download")
)
server <- function(input, output){
output$Download <- downloadHandler(
filename = "Downloads.zip",
content = function(file){
withProgress(message = "Writing Files to Disk. Please wait...", {
temp <- setwd(tempdir())
on.exit(setwd(temp))
files <- c("mtcars.csv", "iris.csv")
write.csv(mtcars, "mtcars.csv")
write.csv(iris, "iris.csv")
zip(zipfile = file, files = files)
})
}
)
}
shinyApp(ui, server)
I've tried wrapping the write.csv inside a future function and setting `and while this does not throw an error, the app is not available for other users during the download.
library(shiny)
library(promises)
library(future)
plan(multiprocess)
ui <- fluidPage(
downloadButton("Download", "Download")
)
server <- function(input, output){
output$Download <- downloadHandler(
filename = "Downloads.zip",
content = function(file){
withProgress(message = "Writing Files to Disk. Please wait...", {
temp <- setwd(tempdir())
on.exit(setwd(temp))
files <- c("mtcars.csv", "iris.csv")
future(write.csv(mtcars, "mtcars.csv"))
future(write.csv(iris, "iris.csv"))
zip(zipfile = file, files = files)
})
}
)
}
shinyApp(ui, server)
I've also tried wrapping the entire downloadHandler function inside the future function, but I get the error:
Error in .subset2(x, "impl")$defineOutput(name, value, label) :
Unexpected MulticoreFuture output for DownloadUnexpected
MultiprocessFuture output for DownloadUnexpected Future output for
DownloadUnexpected environment output for Download
How can I handle the entire downloadHandler asyncronously? I am using the open source version of shiny server.
Don't know if you still need an answer for this, but I think you were very close. I have wrapped both the write.csv and zip in future as below and it works for multiple users on my testing.
library(shiny)
library(promises)
library(future)
plan(multiprocess)
ui <- fluidPage(
downloadButton("Download", "Download")
)
server <- function(input, output){
output$Download <- downloadHandler(
filename = "Downloads.zip",
content = function(file){
withProgress(message = "Writing Files to Disk. Please wait...", {
temp <- setwd(tempdir())
on.exit(setwd(temp))
files <- c("mtcars.csv", "iris.csv")
future({
Sys.sleep(15)
write.csv(mtcars, "mtcars.csv")
write.csv(iris, "iris.csv")
zip(zipfile = file, files = files)})
})
}
)
}
shinyApp(ui, server)
Is it possible to set reactiveValues inside the content part of the downloadHandler? I tried it and don't understand the behavior.
A simple example could be a counter showing how often the download button has been clicked:
library(shiny)
ui <- fluidPage(
downloadButton("downloadData", "Download"),
textOutput("nDownloads"),
actionButton("trig", "get number")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
r.nDownloads <- reactiveValues(n=0)
output$nDownloads <- renderText({
input$trig
paste("number of downloads:", r.nDownloads$n)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
r.nDownloads$n <- r.nDownloads$n + 1
write.csv(data, file)
}
)
}
shinyApp(ui, server)
If the download button is clicked, the textOutput is grayed out, but not updated. I added an action button as a trigger to force the renderText to be updated. Surprisingly (at least to me) that works: the correct number is shown.
So, somehow the reactiveValue is changed by the downloadHandler, but its dependencies are only invalidated, not updated.
Of course, the proper way to do it would be making the "data"-object reactive and doing the counting there. But I'm curious how the described behavior can be explained.
EDIT:
OK, now I get really confused: I tried what I mentioned above: making "data" reactive and doing the counting there. This could not be simple counting of downloads anymore, because the data-reactive gets only recalculated if it's invalid.
Here is an example with an additional input for the invalidation of "data":
library(shiny)
ui <- fluidPage(
numericInput("nRows", label = "nRows", min=1, max=32, value=15),
downloadButton("downloadData", "Download"),
textOutput("nDownloads")
)
server <- function(input, output) {
r.nDownloads <- reactiveValues(n=0)
# Our dataset
data <- reactive({
isolate({
r.nDownloads$n <- r.nDownloads$n + 1
})
mtcars[1:input$nRows,]
})
output$nDownloads <- renderText({
paste("number of downloads:", r.nDownloads$n)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(data(), file)
}
)
}
shinyApp(ui, server)
But still, I see a similar behavior: Clicking the download button grays the text out, changing "nRows" makes the expected number of downloads (which is now downloads after a change in nRows ;-)) to show up.
Now it gets an actual problem for me: In my real app, a rather complex Excel file can be downloaded. While preparing and formatting the Excel file there can occur events that should lead to some reaction of the app. That's why the download should trigger something. The alternative I can see is, to prepare the Excel file before the user clicks on download (what I would like to avoid, because this can take a few seconds depending on the complexity of the file/formatting).
Am I missing something obvious? If not, I'd appreciate any ideas, how the download event can trigger something in the rest of the app.
The solution is to remove the isolation of the reactiveValues as this prevents the counter from being updated until the numericInput is triggered. This is because data() is dependent on input$nrows.
library(shiny)
ui <- fluidPage(
numericInput("nRows", label = "nRows", min = 1, max = 32, value = 15),
downloadButton("downloadData", "Download"),
textOutput("nDownloads")
)
server <- function(input, output) {
r.nDownloads <- reactiveValues(n = 0)
# Our dataset
data <- reactive({
r.nDownloads$n <- r.nDownloads$n + 1
mtcars[1:input$nRows,]
})
output$nDownloads <- renderText({
paste("number of downloads:", r.nDownloads$n)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep = "")
},
content = function(file) {
write.csv(data(), file)
}
)
}
shinyApp(ui, server)
With regards to the deeper problem, it would be inefficient to constantly prepare a complex Excel file if there is no guarantee that the user would download the file. What you can try to do is:
Keep your data in a reactive method (e.g. data()).
Write a method to prep your data for downloading (e.g. prepExcel(data)) which returns your prepped data.
Pass (1) and (2) into the content of the downloadHandler() like this: write_xx(prepExcel(data())) or pipe the data into the write_xx function like this data() %>% prepExcel() %>% write_xx() where xx is the method used to output your final file e.g. write_xlsx or write_csv etc.
I hope this helps.
*Hi, I'm trying to download multiple csv file from a unique excel file. I want to download (using only one downloadbutton) the differents sheets from the excel file.
I don't understand why a for() loop doesn't work, and I can't see how can I do?
If anyone knows..
The point is to download differents csv files, which are in the "wb" list (wb[1],wb[2]...)
Thanks.
Here is my code who works with the third sheet for instance (and sorry for my bad english) :
ui :
library(readxl)
library(shiny)
library(XLConnect)
fluidPage(
titlePanel("Export onglets en CSV"),
sidebarLayout(
sidebarPanel(
fileInput('fichier1','Choisissez votre fichier excel :',
accept = ".xlsx"),
fluidPage(
fluidRow(
column(width = 12,
numericInput("sheet","Indiquez l'onglet à afficher :",min = 1, value = 1),
tags$hr(),
textInput('text',"Indiquez le nom des fichiers :"),
tags$hr(),
h4("Pour télécharger les fichiers .csv :"),
downloadButton("download","Télécharger")
)
)
)),
mainPanel(
tabsetPanel(
tabPanel('Importation',
h4("Fichier de base:"),
dataTableOutput("contents"))
)
)
)
)
Server :
function(input,output){
#Création data :
data <- reactive({
inFile<- input$fichier1
if (is.null(inFile)){
return(NULL)
}else{
file.rename(inFile$datapath,
paste(inFile$datapath,".xlsx", sep =""))
wb = loadWorkbook(paste(inFile$datapath,".xlsx",sep=""))
lst = readWorksheet(wb,sheet = getSheets(wb))
list(wb = wb, lst = lst)
}
})
#Sortie de la table :
output$contents <- renderDataTable({
data()$wb[input$sheet]
},options = list(pageLength = 10))
#Téléchargement :
output$download <- downloadHandler(
#for (i in 1:input$sheet){
filename = function(){
paste(input$text,"_0",3,".csv",sep = "")
},
content = function(file){
write.table(data()$wb[3],file,
sep = ';', row.names = F, col.names = T)
}
#}
)
}
As #BigDataScientist pointed out, you could zip all of your csv file and download the zipped file. Your downloadHandler could look like:
output$download <- downloadHandler(
filename = function(){
paste0(input$text,".zip")
},
content = function(file){
#go to a temp dir to avoid permission issues
owd <- setwd(tempdir())
on.exit(setwd(owd))
files <- NULL;
#loop through the sheets
for (i in 1:input$sheet){
#write each sheet to a csv file, save the name
fileName <- paste(input$text,"_0",i,".csv",sep = "")
write.table(data()$wb[i],fileName,sep = ';', row.names = F, col.names = T)
files <- c(fileName,files)
}
#create the zip file
zip(file,files)
}
)
This does not download all the sheets from the excel file but the sheets ranging from 1 to whatever the user has as input in input$sheet.
You could also disable the download button if the user has not added an excel file/name.
Hope you've solved this MBnn, but in case anyone else is having similar problems, this case is down to RTools not being installed correctly on windows.
Currently you need to play close attention while running through the install process, and make sure to hit the checkbox to edit the system path.
Based on your code, this is likely to be the same issue preventing you from saving XLSX workbooks too.
I know this is an old thread but I had the same issue and the top answer did not work for me. However a simple tweak and using the archive package worked.
Reproductible example below:
library(shiny)
library(archive)
shinyApp(
# ui
ui = fluidPage(downloadButton("dl")),
# server
server = function(input, output, session) {
# download handler
output$dl <- downloadHandler(
filename = function() {"myzipfile.zip"},
# content: iris and mtcars
content = function(file) {
# definition of content to download
to_dl <- list(
# names to use in file names
names = list(a = "iris",
b = "mtcars"),
# data
data = list(a = iris,
b = mtcars)
)
# temp dir for the csv's as we can only create
# an archive from existent files and not data from R
twd <- setwd(tempdir())
on.exit(setwd(twd))
files <- NULL
# loop on data to download and write individual csv's
for (i in c("a", "b")) {
fileName <- paste0(to_dl[["names"]][[i]], ".csv") # csv file name
write.csv(to_dl[["data"]][[i]], fileName) # write csv in temp dir
files <- c(files, fileName) # store written file name
}
# create archive from written files
archive_write_files(file, files)
}
)
}
)
This will create the zip file myzipfile.zip which will contain iris.csv and mtcars.csv.