Reputation: 204
I'm new to Shiny (worked with R for a few months) and i'm trying to figure out what a 'Shiny' way of handling multiple uploaded files is.
My goal is to have an interface where the user can upload multiple files. From these files a report must be generated, in the form of a large number of graphs and plots. From what I learned in the Shiny tutorials, on the server side all objects are isolated from eachother (meaning they cant be read, unless you call another function explicitly).
As you can see in my code below, this means that I have to duplicate the processing code for every single plot. This seems inefficient. What would be the 'Shiny' way of handling this?
Also, i left out a bunch of code that is not absolutely necessary for the example. In essence, I need to do a lot more processing and i dont want to duplicate all that code for every plot.
I am specifically asking about the server-side code. I want to read in multiple files that contain different contents. The operations in the server-side code are just place-holders, I don't actually want to cbind anything but I put that there to keep this code simple. I want to be able to do whatever I want to the imported data frames.
library(shiny)
# Define UI for application
ui <- fluidPage(
# Sidebar with file input
sidebarLayout(
sidebarPanel(
fileInput("people", NULL, multiple = FALSE, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file"),
fileInput("info", NULL, multiple = FALSE, accept = ".csv",
buttonLabel = "Browse...", placeholder = "info file"),
),
# Show the results of the data processing
mainPanel(
imageOutput("plot"),
tableOutput("base_data")
)
)
)
# Define server logic required to process the data
server <- function(input, output) {
output$base_data <- renderTable({
if(is.null(input$people) | is.null(input$info)) {
} else {
people_file <- input$people
info_file <- input$info
people <- read.csv(people_file$datapath, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(info_file$datapath, stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
rbind(people, info)
}
})
output$plot <- renderImage({
if(is.null(input$people) | is.null(input$info)) {
outfile <- tempfile(fileext='.png')
png(outfile, width = 1200, height = 800, res = 200)
dev.off()
list(src = outfile, width = 1200, height = 800)
} else {
people_file <- input$people
info_file <- input$info
people <- read.csv(people_file$datapath, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(info_file$datapath, stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
outfile <- tempfile(fileext='.png')
png(outfile, width = 1200, height = 800, res = 200)
plot(nrow(people), nrow(info), type="b")
dev.off()
list(src = outfile, width = 1200, height = 800, alt = "questions"))
}
}, deleteFile = TRUE)
}
# Run the application
shinyApp(ui = ui, server = server)
Here's an example of what i want in pseudocode:
[[ui]]
fileInput("people")
fileInput("info")
show(plot)
show(plot2)
show(df)
[[serverside]]
files <- source(input) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
}
contents <- plot(output) {
some_function(files$people, files$info)
plot(contents)
}
contents2 <- plot(output) {
some_other_function(files$people, files$info)
plot2 <- plot(contents2)
}
df <- table(output) {
cbind(files$people, files$info)
}
This is in pseudocode what i have now, which is not efficient.
[[ui]]
fileInput("people")
fileInput("info")
show(plot)
show(plot2)
show(df)
[[serverside]]
contents <- plot(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
contents <- some_function(people, info)
plot(contents)
}
contents2 <- plot(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
contents <- some_other_function(people,info)
plot(contents)
}
df <- table(input, output) {
people <- read.csv(input$people, stringsAsFactors = F, encoding = "UTF-8-BOM")
info <- read.csv(input$info, stringsAsFactors = F, encoding = "UTF-8-BOM")
cbind(people, info)
}
Upvotes: 0
Views: 971
Reputation: 17689
I will have to agree with heds1 thats a bit difficult to get your desired result. Since we dont have access to your csvs i created some dummy ones.
Reproducible data / csvs:
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
write.csv2(x = 6:10, file = "people2.csv", row.names = FALSE)
If i understand you correctly you would like to avoid repeating the code for every uploaded file. In order to loop over your files/ datasets we will have to collect them in one data structure.
One way of doing so would be to allow upload multiple files:
fileInput(..., multiple = TRUE)
Ui Side:
The ui side you could create with a loop in renderUI()
:
output$plots <- renderUI({
lapply(paste("people", 1:length(data)), plotOutput)
})
Server side:
The server side you can create with a loop over:
output[[paste("people", nr)]] <- renderPlot({
plot(plotData)
})
Local assignment
Finally you will have to use local()
to avoid that only the data of the last iteration of the loop is taken:
local({
LOCAL_VARIABLE <- data[[nr]]
....
})
Full reproducible example:
library(shiny)
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
write.csv2(x = 6:10, file = "people2.csv", row.names = FALSE)
ui <- fluidPage(
fileInput(inputId = "people", label = NULL, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file", multiple = TRUE),
uiOutput("plots")
)
server <- function(input, output, session) {
observeEvent(input$people, {
data <- lapply(input$people$datapath, read.csv2)
for(nr in 1:length(data)){
local({
plotData <- data[[nr]]
output[[paste("people", nr)]] <- renderPlot({
plot(plotData)
})
})
}
output$plots <- renderUI({
lapply(paste("people", 1:length(data)), plotOutput)
})
})
}
shinyApp(ui, server)
Edit:
Reuse the imported (and transformed) data:
library(shiny)
write.csv2(x = 1:5, file = "people.csv", row.names = FALSE)
ui <- fluidPage(
fileInput(inputId = "people", label = NULL, accept = ".csv",
buttonLabel = "Browse...", placeholder = "people file", multiple = FALSE),
plotOutput("plot"),
tableOutput("table"),
verbatimTextOutput("text")
)
server <- function(input, output, session) {
global <- reactiveValues()
observeEvent(input$people, {
data <- read.csv2(input$people$datapath)
# DO LOTS OF OPERATIONS ON data
global$data <- data
# FROM HERE ON USE: global$data
})
output$plot <- renderPlot({
req(global$data)
plot(global$data)
})
output$table <- renderTable({
global$data
})
output$text <- renderText({
toString(global$data)
})
}
shinyApp(ui, server)
Upvotes: 5