Context: I have an app transforming data according to user's choices. It creates a few tables and plots in the process.
Objective: to save some objects created in the process into one new folder with one click on a button.
Previous researches: the code below saves objects using downloadHandler() and some functions as presented here. It does not seems to allow multiple objects to be passed into downloadHandler(). I am aware it is possible to stack these objects in a list and then save it but if possible I would like to avoid doing it and instead get multiple files (like .txt or .png, ...)
Here is a reproductible example with very little data using datasets included in R (mtcars and iris).
library(shiny)
ui <- fluidPage(
downloadButton("save", "Save") # one click on this button to save df1 AND df2 tables in a new folder
)
server <- function(input, output) {
# my real app does multiple changes on datasets based on user choices
df1 = mtcars[1:10,]
df2 = iris[1:10,]
# Now I want to save df1 and df2 objects with 1 click on the "Save" button
output$save = downloadHandler(
filename = function(){ paste("example", ".txt", sep = " ") },
content = function(file) { write.table(df1, file) }
)
}
# Run the application
shinyApp(ui = ui, server = server)
Many thanks for your help and suggestions!
As noted in the comments of the linked post, it's not typically a good idea to change the working directory (and unnecessary in this case). While inconsequential with a small number of files, the paste0 call to create the path doesn't need to be in the for loop as it is vectorized. This also eliminates the need to dynamically grow the fs vector (also generally a bad practice). Lastly, my zip utility wasn't on my path which caused the utils::zip to fail (you can specify the path in the function call, otherwise it checks for the environment variable R_ZIPCMD and defaults to 'zip' assuming it to be on the path).
I generally agree with the accepted answer, but here's an alternative solution using the zip::zipr function instead (also walk instead of the for loop)
library(shiny)
library(purrr)
library(zip)
ui <- fluidPage(
downloadButton("save", "Save") # one click on this button to save df1 AND df2 tables in a new folder
)
server <- function(input, output) {
# my real app does multiple changes on datasets based on user choices
df1 <- mtcars[1:10,]
df2 <- iris[1:10,]
# need to names these as user won't be able to specify
fileNames <- paste0("sample_", 1:2, ".txt")
output$save = downloadHandler(
filename = function(){ paste0("example", ".zip") },
content = function(file) {
newTmpDir <- tempfile()
if(dir.create(newTmpDir)){
# write data files
walk2(list(df1, df2), fileNames,
~write.table(.x, file.path(newTmpDir, .y))
)
# create archive file
zipr(file, files = list.files(newTmpDir, full.names = TRUE))
}
},
contentType = "application/zip"
)
}
Related
I'm pretty stuck here; I have created a simple shiny app with the possibility of uploading multiple files. However, I don't know how can I move on from here and access the files directly within the shiny app, for example, get all the uploaded data files into one data.frame to perform a loop later on.
for example we have
data_1 <- "data file 1"
data_2 <- "data file 2"
data_3 <- "data file 3"
data_4 <- "data file 4"
dataSet <- data.frame(DATA= c(1,2,3,4),
DATAFILE=c(data_1 ,data_2 ,data_3 ,data_4))
Is there any way to do that? I hope I have been able to explain myself thoroughly. I really appreciate any help you can provide.
library(shiny)
options(shiny.maxRequestSize = 30 * 1024^2)
ui <- fluidPage(
fileInput("upload", NULL, buttonLabel = "Upload...", multiple = TRUE),
tableOutput("files")
)
server <- function(input, output, session) {
output$files <- renderTable(input$upload)
}
shinyApp(ui, server)
input$upload is a data.frame containing four columns, to read the files we'll need datapath column that contains the temp path with the uploaded data, in this case they are csv's. From there we use a function like readr::read_csv() to transform the raw uploaded data into a df.
We can construct a reactive that consists in a list with all the uploaded files in it.
# read all the uploaded files
all_files <- reactive({
req(input$upload)
purrr::map(input$upload$datapath, read_csv) %>%
purrr::set_names(input$upload$name)
})
Full app:
library(shiny)
library(tidyverse)
library(DT)
# create some data to upload
write_csv(mtcars, "mtcars.csv")
write_csv(mpg, "mpg.csv")
write_csv(iris, "iris.csv")
options(shiny.maxRequestSize = 30 * 1024^2)
ui <- fluidPage(
fileInput("upload", NULL, buttonLabel = "Upload...", multiple = TRUE),
DT::DTOutput("files"),
tableOutput("selected_file_table")
)
server <- function(input, output, session) {
output$files <- DT::renderDT({
DT::datatable(input$upload, selection = c("single"))
})
# read all the uploaded files
all_files <- reactive({
req(input$upload)
purrr::map(input$upload$datapath, read_csv) %>%
purrr::set_names(input$upload$name)
})
#select a row in DT files and display the corresponding table
output$selected_file_table <- renderTable({
req(input$upload)
req(input$files_rows_selected)
all_files()[[
input$upload$name[[input$files_rows_selected]]
]]
})
}
shinyApp(ui, server)
There are two stages to this:
When you select a file what happens is that is gets copied into a temp directory. One of the values returned by the input is the location of the temp file, another is the original file name.
Once you have the file path you can use a function to read the data from that temp file.
The example at the bottom of this should be helpful (although your example needs a little bit more than this one because you have selected multiple files):
https://shiny.rstudio.com/reference/shiny/1.6.0/fileInput.html
I'm creating a simple GUI in Shiny for reading in a bunch of csv files and then filtering them by values present in the 5th column of each csv. I'm not sure how to access the correct shiny environment however. For example, within the server function, I first read the files in with the lines:
for (i in all_paths) {
n <- basename(i)
temp = list.files(path = i, pattern="*.csv",full.names = TRUE)
list2env(
lapply(setNames(temp, make.names(gsub(".*FRSTseg*", n, temp))),
read.csv), envir = .GlobalEnv)
}
And then filter with:
Pattern1<-grep("*.csv",names(.GlobalEnv),value=TRUE)
all_data<-do.call("list",mget(Pattern1))
newdfs <- lapply(all_data, function(x) subset(x, x[, 5] > 0))
list2env(newdfs,globalenv())
When I run the app, I get en error message saying it can't find the value of one of my csvs, which I have found to be the first element of the Pattern1 list. So I'm pretty sure the app fails right after the Pattern1 line.
I think the problem is that the csv files are not being read into the correct environment, such that the all_data <- do.call... line does not know where to look. So instead of using .GlobalEnv and globalenv, what should I be using? Any help is appreciated, thanks!
We can use reactiveValues and store the result of read_csv to be available across all observers in the app. I created a small app that reads the 5th column of different .csv files located in the project directory. In this case all the data will be stored inside an object called column_read$files that can be invoked inside any observer or reactive.
app:
library(tidyverse)
library(shiny)
set.seed(15)
#create the data
paste0('iris', 1:5, '.csv') %>%
map(~write_csv(x = slice_sample(iris,n = 10), .x))
ui <- fluidPage(
actionButton('read_files', "Read Files"),
textOutput('columns_print')
)
server <- function(input, output, session) {
columns_read <- reactiveValues(files = NULL)
observeEvent(input$read_files, {
files <- list.files(pattern = "*.csv",full.names = TRUE)
columns_read$files <- map(files, ~read_csv(.x, col_select = 5))
})
output$columns_print <- renderPrint({
req(columns_read$files)
columns_read$files
})
}
shinyApp(ui, server)
After I get the column name from the user, I want to unmerge the column 'alpha.'
I have the dataframe, and I have the same dataframe on my system as a csv file. (I'm highlighted the dataframe here for clarity) -> All of the following is occurring in the r script named test.R
What I'm looking for is a way to use test.R When I click the "Unmerge" button in R shiny, I should get the results (with unmerging columns) and the final dataset should render in the main panel.
Since I'm new to R Shiny, I'm not sure how to go about doing it.
Could someone please help me?
Note: The browse button should take the same data frame as csv and provide the unmerged results in the main panel.
test.R
library(dplyr)
library(tidyr)
library(stringr)
library(tidyverse)
library(stringr)
library(svDialogs)
column_name <- dlg_input("Enter a number", Sys.info()["user"])$res
before_merge<- data.frame(ID=21:23, alpha=c('a b', 'c d', 'e z'))
before_merge
library(reshape2)
newColNames <- c("type1", "type2")
#column_name <- readline(prompt="Enter the desired column name: ")
newCols <- colsplit(before[[column_name]], " ", newColNames)
after_merge <- cbind(before, newCols)
after[[column_name]] <- NULL
after_merge
Shiny App
## Only run examples in interactive R sessions
library(shiny)
if (interactive()) {
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
fileInput("file1", "Choose CSV File", accept = ".csv"),
checkboxInput("header", "Header", TRUE),
actionButton("dataset2", "Extract", class = "btn-primary"),
),
mainPanel(
tableOutput("contents")
)
)
)
server <- function(input, output) {
output$contents <- renderTable({
file <- input$file1
ext <- tools::file_ext(file$datapath)
req(file)
validate(need(ext == "csv", "Please upload a csv file"))
read.csv(file$datapath, header = input$header)
})
observeEvent(input$dataset2, {
source("test.R", local = TRUE)
})
}
shinyApp(ui, server)
}
So what you want to do is twofold:
First, remove the dialog input from test.R and put in a corresponding input in Shiny itself. This is to identify the column, right? You can make a selectInput with the options being the column names that the dataframe has.
Next, put all the relevant code into a function. This function should do the following: take in an input dataframe (you are creating this before_merge in test.R, instead, use the dataframe that you are getting from the upload), do whatever you need to do (the colsplit, etc.), and then return the final result.
Once you have that, then it's just a matter of putting a line at the top of your Shiny file where you source your test.R, and then you can call the function directly.
Alternatively, you don't even need a separate test.R file - just put your function above the output$contents section and use it directly. It's useful to have a separate helper file if you have a lot of functions (or the functions are used elsewhere), but in this case you don't need it.
All the shiny tutorials I see import multiple data manually via fileInput() then export manually.
Currently, I just have a single R script files that I manually change the few variables each time I run it.
For example, at directory C:/Users/Users/Project/000-0000, I want to update 000-0000_result1 and 000-0000_result2 using information from 000-0000_NewData.
#### Variables I change
file_name <- "C:/Users/Users/Project/000-0000/000-0000_NewData.csv"
parameterNum <- 3
#### Rest of the codes that I never change
setwd(dirname(file_name)
projectID <- str_extract(file_name, "[^_]+") #would be 000-0000 in this case
dat0 <- read_csv(file_name)
prev_result1 <- read_csv(str_c(projectID, "_result1"))
prev_result2 <- read_csv(str_c(projectID, "_result2"))
... #data step using parameterNum
write_csv(new_result1, str_c(projectID, "_result1"))
write_csv(new_result2, str_c(projectID, "_result2"))
I want to create a Shiny app where I can just specify the file_name with fileInput("dat0","Upload a new data") and numericInput() then run the rest of the script.
I do not want to manually select multiple files then export them, because I have a lot of _result files mixed with other files sharing the same filetypes.
I was looking at input$dat0$datapath but it seems that shiny creates a tmp folder with only files loaded through fileInput()
Is my plan possible using Shiny? I am using flexdashboard, but I also welcome and will try to adjust standard Shiny answer on my own.
Perhaps something like this:
library(shiny)
library(tidyverse)
ui <- fluidPage(
textInput('file_name', 'Path to filename', value = "C:/Users/Users/Project/000-0000/000-0000_NewData.csv"),
numericInput('parameterNum', 'Insert Parameter Number',value = 3, min = 0),
actionButton(inputId = 'save', label = 'Write csvs')
)
server <- function(input, output, session) {
observe({
setwd(dirname(input$file_name))
})
projectID <- reactive({
str_extract(inpt$file_name, "[^_]+")
})
prev_result1 <- reactive({
read_csv(str_c(projectID(), "_result1"))
#some calculation
})
prev_result2 <- reactive({
read_csv(str_c(projectID(), "_result2"))
#some calculation
})
observeEvent(input$save, {
write_csv(prev_result1(), str_c(projectID(), "_result1"))
write_csv(prev_result2(), str_c(projectID(), "_result2"))
})
}
shinyApp(ui, server)
I would like to know if its possible to create a shiny app which allows you to upload an excel file and which allows you to select a data range based on sheet name and cell range.
I would like to build upon it in order to showcase some regression analysis but haven't been able to find a starting point.
John, it is always a good idea to take a look at the Shiny gallery and take a look at past answers on Stack Overflow for code examples when faced with issues like these.
Here is a example tutorial for data upload. This can be CSV and not just xls.
https://shiny.rstudio.com/gallery/file-upload.html. But code layout may be useful for you to set up your inputs.
Keep it simple? You might be able to save the data range you want out as a csv file so your users do not have specify data range and sheet. I do this so users just simply need to look at what data sets they want in a select box and not go hunt for the data. See example below. (This may save you lots of error trapping code).
Do not forget to transform your data. Note this example where you might need to factor some of your variables.
As outlined above by Parth see https://www.r-bloggers.com/read-excel-files-from-r/ for more detail on packages Xl_Connect and xlsx. You can specify sheets.
WORKING WITH FILES
Some code snippets that may help you. I have the data blocks already available as csv files. Setting up an selectInput with a list of these files
# in ui.R
selectInput(("d1"), "Data:", choices = data.choices)
I fill data.choices in global.R with this code.
# filter on .csv
data.files <- list.files(path = "data", pattern = ".csv")
# dataset choices (later perhaps break by date)
# sort by date most recent so selectInput takes first one
data.choices <- sort(data.files, decreasing = TRUE)
I have a reactive around the selectInput that then loads the data. (I use data.tables package fread so you will need to install this package and use library(data.tables) if you use this code).
dataset1 <- reactive({
validate(
need(input$d1 != "", "Please select a data set")
)
if (!is.null(input$d1)) {
k.filename <- input$d1 # e.g. 'screendata20160405.csv'
isolate({
## part of code this reactive should NOT take dependency on
# LOAD CSV
s.dt <- fread(file.path("data", k.filename),
na.strings = c("NA", "#N/A")) %>%
rename(ticker = Ticker)
# You might choose to rather dot.the.column.names to save DT issues
#setnames(DT, make.names(colnames(DT)))
# SET KEYS IF RELEVANT
k.id.cols <- c("ticker")
if ("date" %in% names(s.dt)) {
k.id.cols <- c(k.id.cols, "date")
}
setkeyv(s.dt, k.id.cols)
# NAME CHANGES rename columns if necessary
setnames(s.dt, "Short Name", "name")
})
} else {
s.dt <- NULL #input$d1 is null
}
s.dt
})
Note the validates as my data is plotted and I want to avoid error messages. Please appreciate the key setting and renaming columns code above is not necessary but specific to my example, but shows you what you can do to get your data "ready" for user.
GET SHEET NAMES OUT
John this is very useful. Take a look at this long thread on google groups https://groups.google.com/forum/#!topic/shiny-discuss/Mj2KFfECBhU
Huidong Tian had this very useful code at 3/17/14 (but also see Stephane Laurent's code about closing XLConnect too to manage memory):
library(XLConnect)
shinyServer(function(input, output) {
Dat <- reactiveValues()
observe({
if (!is.null(input$iFile)) {
inFile <- input$iFile
wb <- loadWorkbook(inFile$datapath)
sheets <- getSheets(wb)
Dat$wb <- wb
Dat$sheets <- sheets
}
})
output$ui <- renderUI({
if (!is.null(Dat$sheets)) {
selectInput(inputId = "sheet", label = "Select a sheet:", choices = Dat$sheets)
}
})
observe({
if (!is.null(Dat$wb)) {
if (!is.null(input$sheet)){
dat <- readWorksheet(Dat$wb, input$sheet)
print(names(dat))
output$columns <- renderUI({
checkboxGroupInput("columns", "Choose columns",
choices = names(dat))
})
}
}
})
})
shinyUI(pageWithSidebar(
# Include css file;
tagList(
tags$head(
tags$title("Upload Data"),
tags$h1("Test")
)
),
# Control panel;
sidebarPanel(
fileInput(inputId = "iFile", label = "Escolha um arquivo:", accept="application/vnd.ms-excel"),
radioButtons("model", "Escolha do Modelo:",
list("CRS" = "crs",
"VRS" = "vrs")),
br(),
tags$hr(),
uiOutput(outputId = "ui"),
uiOutput(outputId = "columns")
),
# Output panel;
mainPanel()
))
You could include inputs for the file path and cell range, and use a shiny action button to send the input variables to read_excel()
https://shiny.rstudio.com/articles/action-buttons.html
http://readxl.tidyverse.org