I've made an shiny app where I'm filtering a dataset using some values and then I would like to be able to download that filtered dataset. However, I'm struggling to understand how I can pass the filtered dataset to the csv downloader. It is a very large dataset so can't use the buttons available in renderDataTable (I think?) Does anyone have any ideas of how I can do this?
Example app:
### data ###
egDf <- data.frame(col1 = sample(letters,10000,replace=T), col2 = sample(letters,10000, replace=T))
### modules ###
chooseCol1UI <- function(id){
ns <- NS(id)
uiOutput(ns('chooserCol1'))
}
chooseCol1 <- function(input, output, session, data){
output$chooserCol1 <- renderUI({
ns <- session$ns
pickerInput(inputId = ns('chosenCol1'),
label = 'Col1',
choices = paste(sort(unique(egDf$col1))),
options = list(`actions-box` = TRUE),
multiple = TRUE)
})
return(reactive(input$chosenCol1))
}
csvDownloadUI <- function(id, label = "Download CSV") {
ns <- NS(id)
downloadButton(ns("downloadData"), label)
}
csvDownload <- function(input, output, session, data) {
output$downloadData <- downloadHandler(
filename = function() {
paste(names(data), Sys.Date(), '.csv', sep='')
},
content = function(file) {
write.csv(data, file, row.names = FALSE)
}
)
}
displayTableUI <- function(id){
ns <- NS(id)
DT::dataTableOutput(ns('displayer'))
}
displayTable <- function(input, output, session, data, col1Input){
output$displayer <- DT::renderDataTable(egDf %>% filter(col1 %in% col1Input()))
}
### server ###
server <- function(input,output){
chosenCol1 <- callModule(chooseCol1,
id = 'appChooseCol1', data = egDf)
callModule(module = displayTable, id = "appDisplayTable",
col1Input = chosenCol1)
}
### ui ###
ui <- fluidPage(
sidebarPanel(
chooseCol1UI("appChooseCol1")),
mainPanel(displayTableUI("appDisplayTable")))
### app ###
shinyApp(ui = ui, server = server)
A few years ago I made an app with such a button. In my case I created a reactive expression in the server.R file that is being passed to the downloadHandler.
Here's the app and here's the github code. Head to the server.R file and search for the "download" string.
In the app you'll find a blue download button in the "Data" tab. The app let's you apply filters that applies in the datatable, that you can download via the button.
Edit: here's the server portion of code of interest:
#data download button
output$idDwn <- downloadHandler(
filename = function() {
paste('uCount ', format(Sys.time(), "%Y-%m-%d %H.%M.%S"), '.csv', sep='')
},
content = function(file) {
write.csv(datasetInputFilters(), file)
}
)
I would create eventReactive function that allows your col1Input.
# Reactive function based on input
react_df <- eventReactive(input$chosenCol1, {
return(egDf %>% filter(col1 %in% input$chosenCol1))
})
output$displayer <- renderDataTable(react_df())
# Download box
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
output_d <- react_df()
write.csv(output_d, file, row.names = FALSE)
}
)
I dealt with this issue recently and unfortunately that solution didn't work for me. But simply using writexl::write_xlsx() instead of write.csv() was enough.
Related
I need a shiny app to do the following:
The user clicks a button
N pop-ups appear to the user asking for input
Then the user downloads the information displayed in the app with a download button
I've been able to achieve points 1 & 2, however I haven't been able to get to 3 because of the fact that the user inputs are reactive values. Here is a sample of code that almost works:
library(shiny)
library(shinyalert)
test <- c("C", "D", "F")
NUM_MODALS <- length(test)
ui <- fluidPage(
shinyalert::useShinyalert(),
actionButton("show", "Show modal dialog"),
lapply(seq(NUM_MODALS), function(id) {
div(id, ":", textOutput(paste0("modal", id), inline = TRUE))
}),
downloadButton("downloadData", "Download")
)
server <- function(input, output) {
observeEvent(input$show, {
for(id in 1:NUM_MODALS){
shinyalert::shinyalert(
type = "input",
text = paste("¿Cuál es la industria de la siguiente empresa?:", test[id]),
inputPlaceholder = "Cuidado con mayúsculas/minúsculas",
inputId = paste0("modal", id)
)
}
})
lapply(seq(NUM_MODALS), function(id) {
output[[paste0("modal", id)]] <- renderText({paste(test[id],input[[paste0("modal", id)]])})
})
export <- reactive(c(input$modal1, input$modal2, input$modal3))
export2 <- isolate(export)
print(export2)
#browser()
output$downloadData <- downloadHandler(
filename = function() {
paste('data-', Sys.Date(), '.csv', sep='')
},
content = function(filesillo) {
fs <- c()
tmpdir <- tempdir()
setwd(tempdir())
path <- paste("prueba.txt", sep = "")
fs <- c(fs, path)
write.csv(export2, filesillo)
}
)
}
shinyApp(ui = ui, server = server)
Instead of the inputs being assigned as a reactive, you can assign to reactiveValues in an observe.
export <- reactiveValues(
dat = NULL
)
observe({
export$dat <- dplyr::bind_rows(
modal1 = input$modal1,
modal2 = input$modal2,
modal3 = input$modal3
)
})
# export <- reactive(c(input$modal1, input$modal2, input$modal3))
# export2 <- isolate(export)
# print(export2)
#browser()
Then in your downloadHandler
#write.csv(export2, filesillo)
write.csv(export$dat, filesillo)
This will output a csv with modal inputs as columns
I am developing a shiny application which save the data entered on the user interface. I have refered the url on shiny rstudio page so by using this page, the code i have written is as mentioned below:
outputDir <- "C:\\Users/dell/Desktop/"
saveData <- function(data) {
data <- t(data)
fileName <- sprintf("%s_%s.csv", as.integer(Sys.time()), digest::digest(data))
write.csv(
x = data, sep = ",",
file = file.path(outputDir, fileName),
row.names = FALSE, quote = TRUE
)
}
loadData <- function() {
files <- list.files(outputDir, full.names = TRUE)
data <- lapply(files, read.csv, stringsAsFactors = FALSE)
data <- do.call(rbind, data)
data
}
library(shiny)
fields <- c("name", "staff_name")
shinyApp(
ui = fluidPage(
titlePanel("attendance System"),
DT::dataTableOutput("responses", width = 300), tags$hr(),
textInput("name", "Accession Number", ""),
selectInput("staff_name", "Staff Name",
c("Rajiv" = "RT",
"Arvind " = "AKS",
"Ashutosh " = "AS")),
actionButton("submit", "Submit")
),
server = function(input, output, session) {
formData <- reactive({
data <- sapply(fields, function(x) input[[x]])
data
})
observeEvent(input$submit, {
saveData(formData())
})
output$responses <- DT::renderDataTable({
input$submit
loadData()
})
}
)
The above code create a new file for each entry. I am looking for a single file in which all entry to be added.
This will give you a unique file name based on time of save and content of the file:
fileName <- sprintf("%s_%s.csv", as.integer(Sys.time()), digest::digest(data))
You can give it a single name like:
fileName <- 'input_bu.csv'
Like #ismirsehregal, I'd recommend bookmarking for this though.
after looking various solutions. I reached at below code to save the data in a single file as it is entered.
library(shiny)
outputDir <- "C:\\Users/dell/Desktop/"
saveData <- function(data) {
data <- as.data.frame(t(data))
if (exists("responsesiq")) {
responsesiq <<- rbind(responsesiq, data)
} else {
responsesiq <<- data
}
fileName <- "test_igntu.csv"
write.csv(
x = responsesiq, sep = ",",
file = file.path(outputDir, fileName),
row.names = FALSE, quote = TRUE
)
}
fields <- c("acc", "staff_name")
shinyApp(
ui = fluidPage(
titlePanel("Attendance System"),
DT::dataTableOutput("responsesiq", width = 300), tags$hr(),
numericInput("acc", "AccNumber", ""),
selectInput("staff_name", "Staff Name",
c("Rajiv" = "RT",
"Arvind" = "AKS",
"Ashutosh" = "AS")),
actionButton("submit", "Submit")
),
server = function(input, output, session) {
# Whenever a field is filled, aggregate all form data
formData <- reactive({
data <- sapply(fields, function(x) input[[x]])
data
})
# When the Submit button is clicked, save the form data
observeEvent(input$submit, {
saveData(formData())
})
}
)
All I am trying to do is read, render and download excel. Not sure how we can display specific UI details such as uploading excel button in dashboard sidebar and rendering and download button in dashboard body
Only error when I tried to get rid this error in mod_exampleUI module function.
Error in mod_example("example_mod") :
argument "output" is missing, with no default
Please find the code below
library(shiny)
library(magrittr) # Load magrittr for the piping operator %>%
library(DT)
library(readxl)
library(tidyselect)
library(writexl)
library(dplyr)
library(tidyr)
library(readxl)
library(stringr)
# Increase band width for shiny to handle bigger file
options(shiny.maxRequestSize=300*1024^2)
# Module UI to display sidebar content
mod_exampleUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
fileInput(ns("file1"), "Choose XLSX File (Convert xls to xlsx)",accept=c(".xlsx")),
tags$hr(),
downloadButton(ns("downloadData"), "Download")
)
}
# Module UI to display Body content
mod_example_displayUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
DT::dataTableOutput(ns("contents"))
)
}
# Function to read all excel sheet necessary
mod_example_display <- function(input, output, session) {
output$contents <- DT::renderDataTable({
DT::datatable(readxl::read_excel(input$file1$datapath)
,options = list(pageLength = 7,scrollX = TRUE))
})
output$downloadData <- downloadHandler(
filename = function() {
paste("updated file dated-", Sys.Date(), ".xlsx")
},
content = function(file) {
write_xlsx(DT::datatable(readxl::read_excel(input$file1$datapath),file))
}
)
}
ui <- fluidPage(
shinydashboard::dashboardPage(
skin = "yellow",
# HEADER -----
shinydashboard::dashboardHeader(
title = "Modularizing App"
),
# SIDEBAR -----
shinydashboard::dashboardSidebar(
shinydashboard::sidebarMenu(
shinydashboard::menuItem('Example', tabName = 'example', icon = shiny::icon('file')),
shinydashboard::tabItems(
shinydashboard::tabItem("example", mod_exampleUI("example_sidemod"))
)
)
),
# BODY -----
shinydashboard::dashboardBody(
shiny::tags$head(shiny::tags$link(rel = "stylesheet", type = "text/css", href = "custom.css")),
shinydashboard::tabItems(
shinydashboard::tabItem("example", mod_example_displayUI("example_bodymod"))
)
)
)
)
server <- function(input, output) {
shiny::callModule(mod_example_display, "mod_example")
}
shinyApp(ui,server)
Pleased to share answer so that it might helkp other shiny developers.
Although it is no elegant but mich appreciated if UI could be improved by experts.
library(shiny)
library(magrittr) # Load magrittr for the piping operator %>%
library(DT)
library(readxl)
library(tidyselect)
library(writexl)
library(dplyr)
library(tidyr)
library(readxl)
library(stringr)
# Increase band width for shiny to handle bigger file
options(shiny.maxRequestSize=30*1024^2)
# Function to read all excel sheet necessary
read_excel_allsheets <- function(filename, tibble = FALSE) {
sheets <- readxl::excel_sheets(filename)
x <- lapply(sheets, function(X) readxl::read_excel(filename, sheet = X, col_names = T, skip = 5
,col_types = "text"
))
if(!tibble) x <- lapply(x, as.data.frame)
names(x) <- sheets
x
}
# Module UI to read content
mod_readUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
fileInput(ns("file1"), h6("Choose xlsx file")
,accept=c(".xlsx"))
)
}
# Module UI to display content
mod_displayUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
DT::dataTableOutput(ns("contents"))
)
}
# Module UI to download content
mod_downloadUI <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
downloadButton(ns("downloadData"), "Download")
)
}
# Server functions
mod_display <- function(input, output, session, file) {
# In case want to alter the data to download
# myfile = reactive({as.data.frame(file()[,1])})
output$contents <- DT::renderDataTable({
DT::datatable(file()
,options = list(pageLength = 7,scrollX = TRUE))
})
reactive({
file()
# myfile()
})
}
mod_read <- function(input, output, session){
getData <- reactive({
req(input$file1)
inFile <- input$file1
mysheets <- read_excel_allsheets(inFile$datapath)
ppm <- mysheets$Download
ppm
})
### In ordert to send data as reactive
reactive({
getData()
})
}
mod_download <- function(input, output, session, displayData){
output$downloadData <- downloadHandler(
# browser(),
filename = function() {
paste("Updated file dated-", Sys.Date(), ".xlsx")
},
content = function(file) {
write_xlsx(displayData(),file)
}
)
}
ui <- fluidPage(
shinydashboard::dashboardPage(
skin = "yellow",
# HEADER -----
shinydashboard::dashboardHeader(
title = "Modularizing App"
),
# SIDEBAR -----
shinydashboard::dashboardSidebar(
shinydashboard::sidebarMenu(id = "menu",
shinydashboard::menuItem('Example', tabName = 'example', icon = shiny::icon('file')),
conditionalPanel("input.menu == 'example'",
shinydashboard::menuSubItem(mod_readUI("sidemod")),
shinydashboard::menuSubItem(mod_downloadUI("downmod"))
)
)
),
# BODY -----
shinydashboard::dashboardBody(
shinydashboard::tabItems(
shinydashboard::tabItem("example", mod_displayUI("bodymod"))
)
)
)
)
server <- function(input, output) {
readFile <- shiny::callModule(mod_read, "sidemod")
displayFile <- shiny::callModule(mod_display, "bodymod", file = readFile)
shiny::callModule(mod_download, "downmod", displayFile)
}
shinyApp(ui,server)
I am working with multiple csv files, and I want to perform some operations on each of the files, and then merge the resultant dataframes into one dataframe and download the output as csv file using Shiny. I decided to reference my script within Shiny using 'source', because the app runs much faster that way instead of putting the code within server.R of shiny. When I run the code without shiny, I got my desired dataframe, but when I run it with Shiny, the columns of the data frame has value. added to the column names. In addition, additional column named "visible" is created. I have tried to add check.names inread,csv() but it does not work. Any help will be appreciated.
These are my dummy excel files. And this is shiny code:
library(shiny)
ui <- fluidPage(
fluidPage(
titlePanel("FOO FILES"),
sidebarLayout(
sidebarPanel(
downloadButton('downloadData', 'Download')
),
mainPanel(
tableOutput('contents')
)
)
)
)
library(shiny)
library(dplyr)
options(shiny.maxRequestSize = 100*1024^2)
server <- function(input, output) {
getData <- reactive({
withProgress(message = 'progress', value = 0, {
# Number of times we'll go through the loop
n <- 100
for (i in 1:n) {
# Increment the progress bar, and update the detail text.
incProgress(1/n)
# Pause for 0.1 seconds to simulate a long computation.
Sys.sleep(0.1)
}
})
source('foo.R')
})
output$downloadData <- downloadHandler(
filename = function() {
paste("katabiz-",
format(Sys.time(), "%Y_%m_%d_%H_%M_%S"), ".csv", sep="")
},
content = function(file) {
write.csv(getData(), file, row.names=FALSE)
})
}
shinyApp(ui = ui, server = server)
The R script (foo.R) that I referenced within Shiny using 'source' is this:
file.list <- list.files(pattern='*.csv')
df.list <- lapply(file.list, read.csv, header = TRUE, quote = "", fill =
FALSE)
numfiles = length(df.list)
kata_csv1 = list()
for (i in 1:numfiles)
{
kata_csv = function(y){
JSON_csv = as.data.frame(df.list[i])
lastrow = nrow(JSON_csv)
shift = function(x, n){
c(x[-(seq(n))], rep(NA, n))
}
JSON_csv$companyID1 = shift(JSON_csv$companyID1, 1)
JSON_csv = JSON_csv[-lastrow, ]
JSON_csv
}
kata_csv1[[i]] = kata_csv(df.list[i])
}
newTable = do.call(rbind, kata_csv1)
colnames(newTable) <- c("companyID1", "name", "NZBN",
"ultimateHoldingCompany", "uhcCountry",
"companyStatus")
newTable
In my previous post, I was able to upload multiple files in Shiny, process the files, rbind the results and return a csv file download, using a for loop. Thanks for the contribution of #SBista. However, because I have to upload a lot of files at a time (total size of about 50 - 100mb), I found running the shiny app to be very slow perhaps due to the for loop. I know that lapply() is faster in reading multiple csv files than for loop, but applying lapply() in my code gives an error (ERROR: Invalid 'description' argument ) after running the app. Any help will be appreciated. This is my dummy file, and this is my code:
library(shiny)
ui <- fluidPage(
fluidPage(
titlePanel("MY CSV FILES MERGER"),
sidebarLayout(
sidebarPanel(
fileInput("file1",
"Choose CSV files from a directory",
multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
downloadButton('downloadData', 'Download')
),
mainPanel(
tableOutput('contents')
)
)
)
)
library(shiny)
library(dplyr)
options(shiny.maxRequestSize = 100*1024^2)
server <- function(input, output) {
getData <- reactive({
inFile <- input$file1
if (is.null(inFile)){
return(NULL)
}else {
files3 = lapply(inFile, function(y){
JSON_csv = read.csv(y, header = TRUE)
lastrow = nrow(JSON_csv)
shift = function(x, n){
c(x[-(seq(n))], rep(NA, n))
}
JSON_csv$companyID1 = shift(JSON_csv$companyID1, 1)
JSON_csv = JSON_csv[-lastrow, ]
JSON_csv
}
)
do.call(rbind, files3)
}
})
output$contents <- renderTable(
getData()
)
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.time(), ".csv", sep="")
},
content = function(file) {
write.csv(getData(), file, row.names=FALSE)
})
}
shinyApp(ui = ui, server = server)
With for loop, this code works but it is very very slow while working with multiple csv files of 50-100mb:
library(shiny)
library(dplyr)
server <- function(input, output) {
getData <- reactive({
inFile <- input$file1
if (is.null(inFile)){
return(NULL)
}else {
# browser()
numfiles = nrow(inFile)
kata_csv1 = list()
for (i in 1:numfiles)
{
JSON_csv = read.csv(input$file1[[i, 'datapath']], header = TRUE)
lastrow = nrow(JSON_csv)
shift = function(x, n){
c(x[-(seq(n))], rep(NA, n))
}
JSON_csv$companyID1 = shift(JSON_csv$companyID1, 1)
kata_csv1[[i]] = JSON_csv[-lastrow, ]
}
# browser()
do.call(rbind, kata_csv1)
}
})
output$contents <- renderTable(
getData()
)
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(getData(), file, row.names=FALSE)
})
}
shinyApp(ui = ui, server = server)
the problem is when you are passing inFile to lapply you are actually passing only the first column containing the filename. Instead you'll need to passinFile$datapath. The lapply should be like this:
files3 = lapply(inFile$datapath, function(y){
JSON_csv = read.csv(y, header = TRUE)
lastrow = nrow(JSON_csv)
shift = function(x, n){
c(x[-(seq(n))], rep(NA, n))
}
JSON_csv$companyID1 = shift(JSON_csv$companyID1, 1)
JSON_csv = JSON_csv[-lastrow, ]
JSON_csv
}
Hope it helps!
Here's a possible solution for files without using for loop:
library(readxl)
file.list <- list.files(pattern='*.xlsx')
df.list <- lapply(file.list, read_excel)
Shiny. I run all my files in a separate script and reference the script using 'source' in the shiny app
source("SCRIPTGEO.R", local = TRUE)
Here's a link on pulling multiple files. Reading multiple files into R - Best practice
Here's what I did on my app in the Script. I'm no expert so there may be other ways...
fils1 <- list.files(pattern = ".csv")
allquotes <- function(fils1){
dfs <- lapply(fils1, function(x){
df <- read.csv(x, header = T, stringsAsFactors = FALSE)
df <- df[-c(1,nrow(df)),]
df <- df[,c(1,2,3,5,6,7,8)]
colnames(df) <- c("ID", "ENTRY_DATE", "QUOTEORORDER","BILL.TO", "NAME", "BRANCH", "CONVERTED")
return(df)
})
testbind <- do.call("rbind", dfs)
return(testbind)
}