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)

MM1
  • 478
  • 15

2 Answers2

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)
Limey
  • 10,234
  • 2
  • 12
  • 32
  • 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 – MM1 Jul 06 '23 at 23:18
  • 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](https://stackoverflow.com/questions/66041663/read-csv-files-more-than-1-in-r-shiny-and-create-new-column-for-filename). Here, you could simply replace the contents of my `lapply` function with the code needed to read one of your files. – Limey Jul 07 '23 at 07:51
  • 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 – MM1 Jul 09 '23 at 01:11
  • 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. – Limey Jul 09 '23 at 17:03
  • Thanks for your persistence with me. Something as easy as that for you may not be easy for others :) – MM1 Jul 10 '23 at 03:34
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.

Grzegorz Sapijaszko
  • 1,913
  • 1
  • 5
  • 12
Styscience
  • 13
  • 4