0

I have a shiny app. I am having trouble displaying the output of multiple csv files in the main panel of the raw data tab. This is my workflow:

  1. Enable the user to select how many groups of datasets to input (done).
  2. Enables the user to select multiple CSV files for each group. E.g. If I have selected 3 in step 1, three fileInput boxes appear to enter the files. (done)
  3. Rbind the files selected for each group. (help!)
  4. Display the rbinded datasets in a new tab (raw data) for each independent group. (help!) The data that is being inputted by the user has the same exact columns.

I'm sure it is a simple solution, I just can't get it working. This is the basis of my code

ui <- fluidPage( theme = bs_theme(version = 4, bootswatch = "pulse"), # Application title titlePanel(HTML("<center>Analysis</center>")), sidebarLayout( sidebarPanel( #begin tab conditionalPanel(condition = "input.tabselected==3", numericInput( "group_num", "How many independent variables are you comparing", 2, min = 1, max = 20 ), uiOutput("groups"), actionButton("save_btn", "Save Variables"), uiOutput("files")), actionButton("save_files", "Save Files"), ), mainPanel( tabsetPanel(type = "tabs", id = "tabselected", selected = 3, #Default tab selected is 3 tabPanel("Begin", textOutput("text1"), verbatimTextOutput("saved_variables"), value = 3), tabPanel("Raw Data", tableOutput("raw_data")), tabPanel("Stats", textOutput("stats_choice")), tabPanel("Data", tableOutput("out_data")), ) ) ) ) server <- function(input, output) { # Generate dynamic text input fields based on the number of groups output$groups <- renderUI({ group_num <- as.integer(input$group_num) lapply( 1:group_num, function(i){ textInput( paste0("group", i), paste0("Enter the name of group ", i) ) } ) }) # Generate dynamic file input fields based on the number of groups output$files <- renderUI({ group_num <- as.integer(input$group_num) lapply( 1:group_num, function(i){ fileInput( paste0("file", i), paste0( "Select the .csv file for group ", i), multiple = TRUE, accept= ".csv" ) } ) }) # Save the text inputs as variables when the button is clicked saved_variables <- reactiveValues() observeEvent(input$save_btn, { group_num <- as.integer(input$group_num) saved_variables$names <- sapply(1:group_num, function(i) input[[paste0("group", i)]]) }) # Generate the output text output$text1 <- renderText({ if (is.null(input$group_num)) { return() } group_num <- as.integer(input$group_num) variables <- saved_variables$names paste0( "You are comparing ", group_num, " independent variables. ", "The groups are called ", paste(variables, collapse = ", "), "." ) }) # Display the saved variables output$saved_variables <- renderPrint({ req(saved_variables$names) saved_variables$names }) output$stats_choice <- renderText({paste0("test4")}) output$out_data <- renderText({paste0("test5")}) } # Run the application shinyApp(ui = ui, server = server) 

2 Answers 2

1

As I wrote in my answer to your previous question, I think you really should be using modules to solve this problem.

Modules allow you to reuse code to do the same tasks over and over again and keep the logic of your main application clean and simple.

You've not shown us the sort of data you have in your csv files, nor the type of analysis you need to do. I've synthesised both: for the data files, I simply generate a random number of random data sets and then row_bind them together, creating an index column (File) to indicate which "file" the observation comes from. For the "analysis", I simply plot two variables, grouped by a third. You can adapt as necessary.

To get started, simply add a tab, click on its header to activate it and then "load" your data. Repeat with another tab. You'll see the data in each tab is different.

I've taken a slightly different approach to you to define the number of tabs required, because I think this way is simpler and lets you focus on the main parts of your questions. It's possible to do it your way if you wish. You can also adapt my approach to delete an arbitrary tab or to insert a new tab in an arbitrary position.

library(shiny) library(tidyverse) # Module UI groupUI <- function(id) { ns <- NS(id) tabPanel( paste0("Group ", id), fileInput( ns("file"), "Select the .csv file(s) for this group", multiple = TRUE, accept = ".csv" ), DT::dataTableOutput(ns("table")), plotOutput(ns("plot")) ) } # Module server groupServer <- function(id) { moduleServer( id, function(input, output, session) { ns <- session$ns csvData <- reactive({ req(input$file) n <- sample(1:5, 1) lapply( 1:n, function(x) { tibble(X = runif(10), Y = rnorm(10), Group = sample(LETTERS[1:2], 10, TRUE)) } ) %>% bind_rows(.id="File") }) output$table <- DT::renderDataTable({ req(csvData()) csvData() }) output$plot <- renderPlot({ req(csvData) csvData() %>% ggplot() + geom_point(aes(x = X, y = Y, colour = Group)) }) } ) } # Application UI ui <- fluidPage( titlePanel("Analysis"), sidebarLayout( sidebarPanel( actionButton("add", "Add group"), actionButton("remove", "Remove last group") ), mainPanel( tabsetPanel(id = "groupTabs") ) ) ) # Application server server <- function(input, output) { rv <- reactiveValues(tabCount = 0, servers = list()) observeEvent(input$add, { rv$tabCount <- rv$tabCount + 1 rv$servers[[rv$tabCount]] <- groupServer(rv$tabCount) appendTab( inputId = "groupTabs", groupUI(rv$tabCount), select = TRUE ) }) observeEvent(input$remove, { removeTab(inputId = "groupTabs", paste0("Group ", rv$tabCount)) rv$servers[[rv$tabCount]] <- NULL rv$tabCount <- rv$tabCount - 1 }) } shinyApp(ui = ui, server = server) 
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks! I like your way a lot better than what I was doing. I have deleted the section where you define n to make a random dataset. Now when I run the application, I can only see the metadata of the file (e.g. file, name, size, type, datapath) rather than the contents of the files. Do you know how to fix this? P.s. the data has the same headings as it is generated from another program. @Limey
It sounds like you've simply deleted my "make random data" code rather than replacing it with code to read the files selected in the fileInput. Reading multiple files from a fileInput is traightforward. Questions on this have been asked - and answered - many times on SO. See, for example, here. Here, you could simply replace the contents of my lapply function with the code needed to read one of your files.
I really appreciate your help @Limey, I have spent the last 6 hours trying to figure this out, and I just can't, even with the other stack overflow questions. Are you able to help any further? I would appreciate your help a lot
I'm struggling to see why you're struggling. Replacing my lapply with lapply(input$file$datapath, read.csv) %>% bind_rows(.id = "File") should give you what you need. If not, perhaps a new question with more details of the exact issue would be appropriate.
Thanks for your persistence with me. Something as easy as that for you may not be easy for others :)
0

Once the user gave you the csv files by group, you can import them and put them in lists, one list for each group. If the csv files have the same columns, and if you don’t mind if the data of each file isn’t separated, you can use rbind(). For each group, you take the first table of the list of the group, you go through the rest of the list and you rbind() the data table to the first table of the group. By doing so, you would have one data table per group and you can put all the tables of each group in a list: so list[[1]] would be the data table for the first group. This list would have generated using reactivity as it uses the user's inputs:

 list_data <- reactive ({ ... }) 

Now to print the data, you have as many outputs as there are groups. So you could use UIOutput("rawdata") instead of tableOutput("rawdata") to display the multiple tables. This way, the number of tableOutput could depend on the number of groups that the user choses. To print the data you would have in the server part :

 library(DT) observeEvent(list_data(), { output$raw_data <- renderUI ({ outputs <- lapply(1:length(list_data()), function(i) { list( DTOutput(paste0("group_", i)) ) } }) do.call(tagList, unlist(outputs, recursive = FALSE)) }) for (i in 1:length(list_data())) { local ({ i <- i output[[paste0("group_", i)]] <- renderDT({ datatable(list_data()[i]) }) }) }) 

Now, if the csv files have different columns, it would be better not to use rbind() but still use the same idea. You do a list of tables per group and a list of lists containing all the groups: list_data[[1]] would be the list of the tables for the group 1 for instance and list_data[[1]][[1]] would be the first table for the group 1. So you would just have to do 2 lapply() to go through each list of the list, and two for loops to display all the tables. You can separate the groups by printing some text with renderText.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.