Save dataframe in Shiny to access it later and download it? - r

I'm new to shiny.
I have a very basic question but I can't find a solution here on stackoverflow.
I am using directory Input function created by wleepang (https://github.com/wleepang/shiny-directory-input).
I wrote a function read_files that rbind all files in the directory selected.
I can display this table with renderTable, this works perfectly. But I do not manage to save this table to work with later (check for missing data, add columns, draw ggplots..) and to download is with write.xlsx
ui <- fluidPage(
directoryInput('directory', label = 'select a directory'),
actionButton("upload", label="Hochladen"),
downloadButton("download", label="Runterladen")
)
server <- function(input, output, session) {
#this part is to set the directory
observeEvent(
ignoreNULL = TRUE,
eventExpr = {
input$directory
},
handlerExpr = {
if (input$directory > 0) {
path = choose.dir(default = readDirectoryInput(session, 'directory'))
updateDirectoryInput(session, 'directory', value = path)
}})
#now comes the actual code
observeEvent(input$upload,{
df <- read_files(readDirectoryInput(session, 'directory'))
})
How can I access this df later?
output$downloadData <- downloadHandler(
filename = function() {
paste('tabelle', '.csv', sep="") },
content = function(file) {
write.xlsx(df, file)
}
)
}
And my second question how can I download it as a xlsx file in the set directory?
my global.r with the read_files function
source('directoryInput.R')
read_files = function(inDir, pat="*.csv", readMe=read.csv2){
files = list.files(inDir, pattern=pat)
files = lapply(files, function(x) file.path(inDir, x))
df = do.call(rbind, lapply(files, readMe))
return(df)
}

I have saved the element using a reactive function
upload_data <- eventReactive(input$upload, {
read_files(readDirectoryInput(session, 'directory')) })
and can access it through upload_data() this works for me

Related

R shiny to download files by selecting the checkbox

There is this piece of code in which basically from UI page in which I want to select the file names for through checkbox and after selecting those, then clicking on download button selected files will get downloaded. I am stuck at UI i am unable to get those checkboxes on UI.
its showing the output as
[object] [Object]
the code is below -
ui <- fluidPage(
verbatimTextOutput("links_list")
)
server <- function(input, output, session) {
get.files <- reactive({
list.files("/Users/harshmeetsingh/Downloads/")
})
obsList <- list()
output$links_list <- renderUI({
lapply(as.list(1:length(get.files())), function(i)
{
btName <- get.files()[i]
print(btName)
# creates an observer only if it doesn't already exists
if (is.null(obsList[[btName]])) {
obsList[[btName]] <<- btName
}
fluidRow(checkboxInput(btName, get.files()[i]) )
})
})
output$downloadzip<-downloadHandler(
filename = function(){
paste0("Extract.zip")
},
content = function(file){
files <- NULL;
for (i in 1:length(obsList)){
if(input[[obsList[[i]]]])
files <- c(paste("output_file/",obsList[[i]],sep=""),files)
}
#create the zip file
zip(file,files)
},
contentType = "application/zip"
)
tempText <- eventReactive({input$TempTest},{
l<-c()
for (i in 1:length(obsList)){
if(input[[obsList[[i]]]])
l<-c(l,paste("output_file/",obsList[[i]],sep=""))
}
return(paste(l) )
},
ignoreInit = TRUE)
output$Temp <- renderPrint({ tempText()})
}
shinyApp(ui=ui,server=server)
We can use checkboxGroupInput() to select all the files. input$files_chosen will be a list with all the filenames selected.
Notice that this app is showing the files in the home directory. This can be modified changing the path supplied in setwd().
app:
library(shiny)
#to use relative paths inside zip function
setwd('~')
ui <- fluidPage(
downloadButton('downloadzip'),
uiOutput("links_list")
)
server <- function(input, output, session) {
get.files <- reactive({
list.files()
})
output$links_list <- renderUI({checkboxGroupInput(inputId = 'files_chosen',
label = 'Choose Files',
choices = get.files())
})
output$downloadzip <- downloadHandler(
filename = function(){
"Extract.zip"
},
content = function(file){
#create the paths to look for the files.
files <- input$files_chosen
#create the zip file
zip(zipfile = file, files = files)
},
contentType = "application/zip"
)
}
shinyApp(ui=ui,server=server)

Use multiple reactive datasets to write to an excel workbook under downloadHandler shiny

Good afternoon, thank you in advance for taking the time to read my question. In my Shiny app, I am trying to create a list of the reactive objects to write to a single excel workbook for the user to download. I was able to use parts of responses from other posts to replicate my issue and I get pretty close to a resolution. However, while the example below uses a list of dataframes, such as mtcars, iris, etc, I am trying to use reactive datasets, such as datasetInput1(), datasetInput2(), etc.
shinyApp(
ui = fluidPage(
downloadButton("downloadExcelSheet", "Download Excel Workbook with Multiple Sheets")
),
server = function(input, output) {
#### Write an Excel workbook with one sheet per dataframe ####
output$downloadExcelSheet <- downloadHandler(
filename = function() {
"excelWorkbook.xlsx"
},
content = function(file) {
# write workbook and first sheet
write.xlsx(mtcars, file, sheetName = "mtcars", append = FALSE)
# add other sheets for each dataframe
listOtherFiles <- list(iris = iris,
airquality = airquality,
sleep = sleep)
for(i in 1:length(listOtherFiles)) {
write.xlsx(listOtherFiles[i], file,
sheetName = names(listOtherFiles)[i], append = TRUE)
}
}
)
When I try to use these reactive objects in the example below, I am able to successfully download the data when there is just one dataset in the list. For example, the below works, but once I start adding more to the list listOtherFiles such as listOtherFiles <- list(datasetInput2(), datasetInput3()), I get an error.
shinyApp(
ui = fluidPage(
downloadButton("downloadExcelSheet", "Download Excel Workbook with Multiple Sheets")
),
server = function(input, output) {
datasetInput1 <- reactive({
data %>%
filter(sub_date == input$date, app_type == input$type)
})
datasetInput2 <- reactive({
data2 %>%
filter(sub_date == input$date, app_type == input$type)
})
output$downloadExcelSheet <- downloadHandler(
filename = function() {
"datasetOutput.xlsx"
},
content = function(file) {
# write workbook and first sheet
write.xlsx(datasetInput1(), file, sheetName = "dataset1", append = FALSE)
# add other sheets for each dataframe
listOtherFiles <- list(datasetInput2())
for(i in 1:length(listOtherFiles)) {
write.xlsx(listOtherFiles[i], file,
sheetName = names(listOtherFiles)[i], append = TRUE)
}
}
)
I'm not sure I can reproduce the problem. Here is my example below. This seems to work and uses two reactive expressions. Does it work for you?
If not, please edit your question and describe further. Perhaps include example data and ui with inputs to reproduce. What was your error?
library(xlsx)
library(shiny)
library(tidyverse)
shinyApp(
ui = fluidPage(
downloadButton("downloadExcelSheet", "Download Excel Workbook with Multiple Sheets")
),
server = function(input, output) {
datasetInput1 <- reactive({
iris %>%
filter(Species == "setosa")
})
datasetInput2 <- reactive({
iris %>%
filter(Species == "versicolor")
})
#### Write an Excel workbook with one sheet per dataframe ####
output$downloadExcelSheet <- downloadHandler(
filename = function() {
"excelWorkbook.xlsx"
},
content = function(file) {
# write workbook and first sheet
write.xlsx(mtcars, file, sheetName = "mtcars", append = FALSE)
# add other sheets for each dataframe
listOtherFiles <- list(setosa = datasetInput1(), versicolor = datasetInput2())
for(i in 1:length(listOtherFiles)) {
write.xlsx(listOtherFiles[[i]], file,
sheetName = names(listOtherFiles)[i], append = TRUE)
}
}
)
}
)
datasetInput1() is a reactive value not defined within the server logic. This needs to be assigned a value first or a function created to update values.
The following are some useful articles to understand shiny's reactive elements:
https://shiny.rstudio.com/articles/understanding-reactivity.html
https://shiny.rstudio.com/articles/reactivity-overview.html

Passing reactive data to global environment

I want to use Shiny within RMarkdown for users to upload data (xlsx file).
Then I want to pass all the worksheets as R data frames (w/o reactivity) to run rest of the RMarkdown file.
I mainly want to convert them into data frames so I can use reticulate to run Python code as well.
I've tried this, and it doesn't seem to quite work:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL, multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
observeEvent(input$done, {
stopApp(c(wb()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()
df1 <- readWorksheet(object = test, sheet = "sheet1")
df2 <- readWorksheet(object = test, sheet = "sheet2")
It throws this error:
Error in (function (classes, fdef, mtable) :
unable to find an inherited method for function ‘readWorksheet’ for signature ‘"list", "character"’
I can return one sheet at a time using stopApp(readWorksheet(object = wb(), sheet = "sheet1")), but I can't seem to return an entire workbook or multiple data frames at the same time.
I don't really want to read in xlsx, save each sheet as csv in working directory, then read those files in again.
Would anyone have a good suggestion on how to get around this?
The documentation of fileInput() states in the details:
datapath
The path to a temp file that contains the data that was
uploaded. This file may be deleted if the user performs another upload
operation.
Meaning that the datapath given in the input variable is a temporary file that is no longer accessible after you close the App, which is what the function readWorksheet will try to do.
So you'll have to read the sheets in the server and return the dataframes somehow.
I did that by defining a second reactive value which is basically a list of dataframes returned by applying lapply on all the sheets in wb, in this case test will be this list of data frames.
There might be other ways (more efficient, or suits your purpose better) to do this, but here it is:
library(dplyr)
library(miniUI)
library(shiny)
library(XLConnect)
launch_shiny <- function() {
ui <- miniPage(
gadgetTitleBar("Input Data"),
miniContentPanel(
fileInput(inputId = "my.file", label = NULL,
multiple = FALSE)
)
)
server <- function(input, output, session) {
wb <- reactive({
new.file <- input$my.file
loadWorkbook(
filename = new.file$datapath,
create = FALSE,
password = NULL
)
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
df_lst <- reactive({
# read all sheets into a list
lapply(getSheets(wb()),
function(sheet){
readWorksheet(object = wb(),
sheet = sheet)
})
})
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
observeEvent(input$done, {
# get the list of dfs from the app
stopApp(c(df_lst()))
})
}
runGadget(ui, server)
}
test <- launch_shiny()

Save a file via sink function in shiny server?

I have a shiny app to generate a .txt file to download.
In addition, I would like to keep a copy of the file that users generate in my shiny server.
the server function looks like :
server <- function(input, output, session){
data_gen <- reactive({
d1= data.frame(...)
d2= data.frame(...)
result <- list(d1=d1, d2=d2)
return(result)
})
create_file <- reactive({
sink("/srv/shiny-server/S3/file.txt",append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
})
output$downloadData <- downloadHandler(
filename = function() {"input.txt"},
content = function(file) {
sink(file,append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
}
)
}
I'm able to download the data but the app does not react to the create_file function and it does not write a copy into shiny server.
Any Idea how could I fix this ?
Your create_file function is a reactive. Reactive functions only evaluate when 1) their output is required, and 2) their inputs have changed. Neither appears to apply here.
What you could do is move the contents of create_file inside your downloadhandler. content must receive a function that returns a file, but the function can do other things first. So try the following:
server <- function(input, output, session){
data_gen <- reactive({
d1= data.frame(...)
d2= data.frame(...)
result <- list(d1=d1, d2=d2)
return(result)
})
output$downloadData <- downloadHandler(
filename = function() {"input.txt"},
content = function(file) {
# save non-user copy
sink("/srv/shiny-server/S3/file.txt",append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
# copy to be returned for user
sink(file,append = TRUE)
print(data_gen()$d1)
print(data_gen()$d2)
sink()
})
}

DownloadHandler function in R package shiny does not produce a csv file for downloading

In my below example of a simple shiny app i recently created, im currently trying to include also the possibility of downloading a data frame that is created from the results. I mention here part of the server.R script in order to not make a huge post:
shinyServer(function(input, output) {
table_options<- list(lengthMenu = list(c(5,10,15,20),
c('5','10', '15', '20')), pageLength = 15, ordering=TRUE,
class = 'cell-border stripe',dom ='t',scrollX = TRUE,
fixedColumns = list(leftColumns = 2, rightColumns = 1))
inTable <- reactive({# upload a tsv file
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.table(inFile$datapath,header=input$header,
sep="\t",stringsAsFactors = FALSE)
}) #END REACTIVE
rv <- reactiveValues()
rv$data <- NULL # to further use it into the observeEvent below
observeEvent(input$goButton, {
df <- inTable()
# some data manipulation with df...
if(input$repo_option=="mimic"){
# some functions here that result to a data frame named final dat
rv$data <- final.dat
rv$data
}
else if(input$repo_option=="reverse"){
# similar procedure...
rv$data <- final.dat
rv$data
}
})
output$contents <- DT::renderDataTable({
expr=DT::datatable(rv$data, options=table_options,
extensions ='FixedColumns',selection="none")
})
output$downloadData <- downloadHandler(
filename = function() { paste("input$file1", ".csv", sep=",") },
content = function(file) {
write.csv(rv$data,file)
}
)
})
My main issue is that, although the output$contents works fine in the app, when i press the download button from the ui.R server (not posted here for simplicity), the download "pop-up" window appears, but the saving does not work. Thus, i suspect that is something wrong with the code in the downloadHandler function, but any ideas or help ?

Resources