3

I have a dashboard where user can upload an .xlsx file and then select columns. Moreover, you can select another column from global variable. If the 2nd column's values don't match to first column, then the cells of 2nd column's row get highlighted. On the start of the application, things work well, but when I select columns again and hit the action button, I see this error in the console Warning: Error in : Can't extract columns that don't exist. But again, as soon as I hit the action button datatable renders in the mainPanel just fine.

My dput of iris.xlsx looks like this-

structure(list(date = structure(c(15706, 15707, 15708, 15723, 
15740, 15741, 15742, 15771, 15791, 15792, 15855), class = "Date"), 
    Sepal.Length = c(5.1, 4.9, 4.7, 5.1, 4.9, 5, 5.5, 6.7, 6, 
    6.7, 5.9), Sepal.Width = c(3.5, NA, NA, NA, NA, NA, NA, 3.1, 
    3.4, 3.1, 3), Petal.Length = c(1.4, 1.4, 1.3, 1.4, 1.5, 1.2, 
    1.3, 4.4, 4.5, 4.7, 5.1), Petal.Width = c(0.2, 0.2, 0.2, 
    0.3, 0.2, 0.2, 0.2, 1.4, 1.6, 1.5, 1.8), Species = c("setosa", 
    "setosa", "setosa", "setosa", "setosa", "setosa", "setosa", 
    "versicolor", "versicolor", "versicolor", "virginica")), row.names = c(NA, 
11L), class = "data.frame")

Here is my reprex-

library(shiny)
library(openxlsx)
library(shinyjs)
library(htmltools)
library(lubridate)
library(DT)
library(dplyr)

#--------------------
#global.R

local_iris <- data.frame(Date= lubridate::mdy(c("1/1/2013","1/2/2013","3/27/2013","3/28/2013",
                                                "1/18/2013","2/4/2013","2/5/2013","2/6/2013")),
                         SPECLENTH= c(5.1,4.9,4.7,4.6,5,5.4,4.6,5.1),
                         SPECWIDTH= c(3,7,6, 8,8,9,5,1))

#-------------

ui <- fluidPage(
  
  sidebarLayout(
    sidebarPanel(
      
      fileInput("xlsxfile", "Choose an xlsx file",
                accept = c(".xlsx")),
      
      tags$hr(),
      
      
      # Input: Select number of rows to display ----
      selectInput("disp", "Display",
                  choices = c(All = "all",
                              Head = "head"),
                  selected = "all"),
      
      # Select variables to display ----
      uiOutput("checkbox"),
      uiOutput("checkbox_2"),
      tags$hr(),
      uiOutput("joinnow") # instead of conditionalPanel
    ),
    mainPanel(
      DT::DTOutput("contents")
    )
  )
)


server <- function(input, output) {
  
  # File handler ----
  mydata <- reactive({
    req(input$xlsxfile)
    inFile <- input$xlsxfile
    
    
    
    req(input$xlsxfile,
        file.exists(input$xlsxfile$datapath))
    
    openxlsx::read.xlsx(xlsxFile = inFile$datapath,
                        sheet = 1 ,
                        detectDates = TRUE,
                        sep.names = "_")
    
  })
  
  # Dynamically generate UI input when data is uploaded, only sow numeric columns ----
  output$checkbox <- renderUI({
    
    selectInput(inputId = "select_var", 
                label = "Select variables", 
                choices = c("", names(mydata() %>%
                                        dplyr::select_if(is.numeric))),
                selected = NULL, 
                multiple = FALSE)
  })
  
  # Select columns to print ----
  df_sel <- reactive({
    req(input$select_var)
    df_sel <- mydata() %>% 
      dplyr::select(input$select_var, date)
  })
  
  
  # Same as above but for global.R variable  ----
  output$checkbox_2 <- renderUI({
    
    if (is.null(mydata())) return(NULL)
    
    selectInput(inputId = "select_var_2", 
                label = "Select variables", 
                choices = c("", names(local_iris %>%
                                        dplyr::select_if(is.numeric))),
                selected = NULL, 
                multiple = FALSE)
  })
  
  
  df_sel_global <- reactive({
    
    req(input$select_var_2)
    
    df_sel_global <- local_iris %>% 
      dplyr::select(input$select_var_2, Date)
  })
  
  output$joinnow <- renderUI({
    if (is.null(input$xlsxfile)) return()
    actionButton("action", "Press after selecting variables")
  })
  
  # Join the dataframes together based on a key  ----
  joined_dfs <-  eventReactive(input$action, {
    
    df_joi <- dplyr::inner_join(df_sel(), df_sel_global(), by= c("date" = "Date")) %>%
      dplyr::select(date,input$select_var,input$select_var_2)
    
  })
  
  # Render data frame ----
  
  matched_val <- reactive({ 
    req(input$action, input$select_var, input$select_var_2)
      ifelse(joined_dfs()%>%
               dplyr::pull(input$select_var) != joined_dfs()%>%
               dplyr::pull(input$select_var_2),
             yes= joined_dfs()%>%
               dplyr::pull(input$select_var_2),
             no= -979025189201)
  })
  
  
  output$contents <- DT::renderDT(server = FALSE, {
    
    req(input$action)
    
    DT::datatable(
      if(input$disp == "head") {
        head(joined_dfs())
      }
      else {
        joined_dfs()
      }, filter = 'top', 
      extensions = c('Buttons'),
      options = list(scrollY = 600,
                     scrollX = TRUE,
                     pageLength = 20,
                     dom =  '<"float-left"l><"float-right"f>rt<"row"<"col-sm-4"B><"col-sm-4"i><"col-sm-4"p>>',
                     lengthMenu=  list(c(20, 40, 60, -1), 
                                       c('20', '40', '60','All')),
                     scrollCollapse= TRUE,
                     lengthChange = TRUE, 
                     widthChange= TRUE,
                     rownames = TRUE)
    ) %>%
      formatStyle(
        columns = 3,
        backgroundColor = styleEqual(levels = matched_val(), values = rep("yellow", length(matched_val())))
      )
    
    
  })
  
  
}

# Run  ----
shinyApp(ui, server)

Thank you for your help.

WannabeSmith
  • 435
  • 4
  • 18

1 Answers1

1

There are couple of issues -

  1. openxlsx::read.xlsx doesn't read the column names of the xlsx file. I have switched to readxl::read_excel.

  2. When you select in the reactive expression it changes the data so the new columns are not available for next selection. Hence, you get the warning. Perform selection at the end while displaying the table in DT::renderDT.


local_iris <- data.frame(Date= lubridate::mdy(c("1/1/2013","1/2/2013","3/27/2013","3/28/2013",
                                                "1/18/2013","2/4/2013","2/5/2013","2/6/2013")),
                         SPECLENTH= c(5.1,4.9,4.7,4.6,5,5.4,4.6,5.1),
                         SPECWIDTH= c(3,7,6, 8,8,9,5,1))

#-------------

ui <- fluidPage(
  
  sidebarLayout(
    sidebarPanel(
      
      fileInput("xlsxfile", "Choose an xlsx file",
                accept = c(".xlsx")),
      
      tags$hr(),
      
      
      # Input: Select number of rows to display ----
      selectInput("disp", "Display",
                  choices = c(All = "all",
                              Head = "head"),
                  selected = "all"),
      
      # Select variables to display ----
      uiOutput("checkbox"),
      uiOutput("checkbox_2"),
      tags$hr(),
      uiOutput("joinnow") # instead of conditionalPanel
    ),
    mainPanel(
      DT::DTOutput("contents")
    )
  )
)


server <- function(input, output) {
  
  # File handler ----
  mydata <- reactive({
    req(input$xlsxfile)
    inFile <- input$xlsxfile
    
    req(input$xlsxfile,
        file.exists(input$xlsxfile$datapath))
    readxl::read_excel(inFile$datapath)
    
  })
  
  # Dynamically generate UI input when data is uploaded, only sow numeric columns ----
  output$checkbox <- renderUI({
    
    selectInput(inputId = "select_var", 
                label = "Select variables", 
                choices = c("", names(mydata() %>%
                                        dplyr::select_if(is.numeric))),
                selected = NULL, 
                multiple = FALSE)
  })
  
  # Select columns to print ----
  df_sel <- reactive({
    req(input$select_var)
    
    df_sel <- mydata() 
    df_sel
  })
  
  
  # Same as above but for global.R variable  ----
  output$checkbox_2 <- renderUI({
    
    if (is.null(mydata())) return(NULL)
    
    selectInput(inputId = "select_var_2", 
                label = "Select variables", 
                choices = c("", names(local_iris %>%
                                        dplyr::select_if(is.numeric))),
                selected = NULL, 
                multiple = FALSE)
  })
  
  
  df_sel_global <- reactive({
    req(input$select_var_2)
    local_iris 
  })
  
  output$joinnow <- renderUI({
    if (is.null(input$xlsxfile)) return()
    actionButton("action", "Press after selecting variables")
  })
  
  # Join the dataframes together based on a key  ----
  joined_dfs <-  eventReactive(input$action, {
    df_joi <- dplyr::inner_join(df_sel(), df_sel_global(), by= c("date" = "Date")) 
    df_joi
    
  })
  
  # Render data frame ----
  
  matched_val <- reactive({ 
    req(input$action, input$select_var, input$select_var_2)
    ifelse(joined_dfs()%>%
             dplyr::pull(input$select_var) != joined_dfs()%>%
             dplyr::pull(input$select_var_2),
           yes= joined_dfs()%>%
             dplyr::pull(input$select_var_2),
           no= -979025189201)
  })
  
  
  output$contents <- DT::renderDT(server = FALSE, {
    
    req(input$action)
    
    DT::datatable(
      if(input$disp == "head") {
        head(joined_dfs()%>%
          dplyr::select(date,input$select_var,input$select_var_2))
      }
      else {
        joined_dfs() %>%
          dplyr::select(date,input$select_var,input$select_var_2)
      }, filter = 'top', 
      extensions = c('Buttons'),
      options = list(scrollY = 600,
                     scrollX = TRUE,
                     pageLength = 20,
                     dom =  '<"float-left"l><"float-right"f>rt<"row"<"col-sm-4"B><"col-sm-4"i><"col-sm-4"p>>',
                     lengthMenu=  list(c(20, 40, 60, -1), 
                                       c('20', '40', '60','All')),
                     scrollCollapse= TRUE,
                     lengthChange = TRUE, 
                     widthChange= TRUE,
                     rownames = TRUE)
    ) %>%
      formatStyle(
        columns = 3,
        backgroundColor = styleEqual(levels = matched_val(), values = rep("yellow", length(matched_val())))
      )
    
    
  })
  
  
}

# Run  ----
shinyApp(ui, server)
WannabeSmith
  • 435
  • 4
  • 18
Ronak Shah
  • 377,200
  • 20
  • 156
  • 213
  • Hi Ronak! Thank you for your help. This method makes the `action` button a bit pointless after its initial use. The table loads/updates right away without hitting the button after each selection after the file gets uploaded. I should just remove the button in that case- doesn't make sense now. Also one less thing to click the better. – WannabeSmith Jul 19 '21 at 12:22
  • Do you think there is a way where we can still utilise that "press button to show table" feature after the initial upload? The reason why I put it there is due to some inconsistent columns in the original dataset. So the idea behind the button was, after user selects column, they may check their selection before they hit the button- causing joins to work properly on columns that matter. – WannabeSmith Jul 19 '21 at 12:32
  • 1
    In your case, there are too many reactive dependencies on one another but usually I would load `output$contents` in `observeEvent(input$action {...})` So the output changes on click of action button. – Ronak Shah Jul 19 '21 at 13:01
  • Oh I see what you mean. Dang it, wish I could have design it better. Regardless, appreciate your help buddy. Take care :) Thank you again! – WannabeSmith Jul 19 '21 at 13:13