Editing data with Shiny: `Warning: Error in [: incorrect number of dimensions`

Hello, my goal is to build a Shiny app where I can upload two dataframes and the app will:

  • Automatically highlight discrepancies between the two dataframes
  • Allow direct editing to the dataframe
  • Allow me to download the newly-edited dataframe

I have created a working reprex using datatable that will highlight discrepant cells and almost allows for editing capability, but the below code generates an error message as soon as I make any edits: Warning: Error in [: incorrect number of dimensions . Any advice on resolving this error is appreciated. I previously tried solving this issue with gt , but I don't believe gt is suitable for editing capability.

library(shiny)
library(shinythemes)
library(data.table)
library(DT)
library(tidyverse)

dat1 <- data.frame(
  emp_id = c(1:5),
  emp_name = c("Rick","Dan","Michelle","Ryan","Gary"),
  salary = c(623.3,515.2,611.0,735.0,844.25))

dat2 <- data.frame(
  emp_id = c(1:5),
  emp_name = c("Rick","Dan","Michelle","Ryan","Gary"),
  salary = c(623.3,515.2,611.0,729.0,843.25))

ui <- navbarPage("This is my Shiny app.", 
                  theme = shinytheme("flatly"), 
                  tabPanel("Upload", 
                           titlePanel("Upload your datafiles"),
                           sidebarLayout(
                             sidebarPanel(
                               
                               ## File 1
                               fileInput('file1', 'Data Entry #1',
                                         accept=c('text/csv', 
                                                  'text/comma-separated-values,text/plain', 
                                                  '.csv')),
                               tags$hr(),
                               
                               ## File 2
                               fileInput('file2', 'Data Entry #2',
                                         accept=c('text/csv', 
                                                  'text/comma-separated-values,text/plain', 
                                                  '.csv')),
                               tags$hr(),
                               downloadButton("download")
                               
                             ),
                             
                             mainPanel(
                               DT::dataTableOutput("contents"),
                               verbatimTextOutput("print"))
                           )
                  ),
)

server <- function(input, output, session) {
  
  df1 <- reactive({ dat1
  # inFile <- input$file1
  #  if (is.null(input$file1))
  #    return(NULL)
  #  read.csv(inFile$datapath)
  })
  
  df2 <- reactive({ dat2
  # inFile <- input$file2
  #  if (is.null(input$file2)) 
  #    return(NULL) 
  #  read.csv(inFile$datapath)
  })

  vals <- reactiveValues(x = NULL)
  
  observe({

    req(df1())
    req(df2())
    
    tbl_diffs <- which(df1() != df2(), arr.ind = TRUE)
    tbl_compare <- df2() %>% DT::datatable(selection = 'none', rownames = FALSE, edit = TRUE)
    for (i in seq_len(nrow(tbl_diffs))) {
      tbl_compare <- tbl_compare %>%
        formatStyle(
          columns = tbl_diffs[[i, "col"]], 
          backgroundColor = styleRow(tbl_diffs[[i, "row"]], c('yellow')))
    } 
    vals$x <- tbl_compare
  })
  
  output$print <- renderPrint({ vals$x })
  output$contents <- DT::renderDataTable(vals$x)

  proxy <- dataTableProxy("contents")
  
  observeEvent(input$contents_cell_edit, {
    info = input$contents_cell_edit
    str(info)
    i = info$row
    j = info$col + 1
    v = info$value
    vals$x[i, j] <<- DT:::coerceValue(v, vals$x[i, j])
    replaceData(proxy, vals$x, resetPaging = FALSE, rownames = FALSE)
  })
  
  output$download <- downloadHandler("example.csv", 
                                     content = function(file){
                                       write.csv(vals$x, file, row.names = F)
                                     },
                                     contentType = "text/csv")
  
}

shinyApp(ui = ui, server = server)

Hello,

I recently worked on a Shiny app that addresses your second bullet point. I used DTedit, a package that allows you to create editable datatables for Shiny apps. I recommend you use David Fong's branch of DTedit, which seems to work better and has more documentation on RPubs.

Thank you for linking DTedit; it looks like it would allow for very flexible editing but I couldn't find a way to make it compatible with conditional formatting when displaying the dataframe.

1 Like

I resolved my issue and have a functioning reprex below that allows for editing, highlighting, and downloading. I believe the core issue was making sure that the highlighted datatable object was being displayed but the dataframe element of the datatable (val$x$x$data) was specifically being edited and downloaded (not the whole datatable itself).

library(shiny)
library(shinythemes)
library(data.table)
library(DT)
library(tidyverse)

dat1 <- data.frame(
  emp_id = c(1:5),
  emp_name = c("Rick","Dan","Michelle","Ryan","Gary"),
  salary = c(623.3,515.2,611.0,735.0,844.25))

dat2 <- data.frame(
  emp_id = c(1:5),
  emp_name = c("Rick","Dan","Michelle","Ryan","Gary"),
  salary = c(623.3,515.2,611.0,729.0,843.25))

ui <- navbarPage("This is my Shiny app.", 
                 theme = shinytheme("flatly"), 
                 tabPanel("Upload", 
                          titlePanel("Upload your datafiles"),
                          sidebarLayout(
                            sidebarPanel(
                              
                              ## File 1
                              fileInput('file1', 'Data Entry #1',
                                        accept=c('text/csv', 
                                                 'text/comma-separated-values,text/plain', 
                                                 '.csv')),
                              tags$hr(),
                              
                              ## File 2
                              fileInput('file2', 'Data Entry #2',
                                        accept=c('text/csv', 
                                                 'text/comma-separated-values,text/plain', 
                                                 '.csv')),
                              tags$hr(),
                              downloadButton("download")
                              
                            ),
                            
                            mainPanel(
                              DT::DTOutput("print"))
                          )
                 ),
)

server <- function(input, output, session) {
  
  df1 <- reactive({ dat1
   # inFile <- input$file1
  #  if (is.null(input$file1))
  #    return(NULL)
  #  readxl::read_excel(inFile$datapath)
  })
  
  df2 <- reactive({ dat2
   # inFile <- input$file2
   # if (is.null(input$file2)) 
  #    return(NULL) 
  #  readxl::read_excel(inFile$datapath)
  })
  
  vals <- reactiveValues(x = NULL)
  
  observe({
    
    req(df1())
    req(df2())
    
    tbl_diffs <- which(df1() != df2(), arr.ind = TRUE)
    tbl_compare <- df2() %>% DT::datatable(selection = 'none', rownames = FALSE, edit = TRUE)
    for (i in seq_len(nrow(tbl_diffs))) {
      tbl_compare <- tbl_compare %>%
        formatStyle(
          columns = tbl_diffs[[i, "col"]], 
          backgroundColor = styleRow(tbl_diffs[[i, "row"]], c('yellow')))
    } 
    vals$x <- tbl_compare
  })
  
  output$print <- DT::renderDT({ vals$x })
  output$contents <- DT::renderDataTable(vals$x)
  
  proxy <- dataTableProxy("contents")
  
  observeEvent(input$print_cell_edit, {
    info = input$print_cell_edit
    str(info)
    i = info$row
    j = info$col + 1
    v = info$value
    vals$x$x$data[i, j] <<- DT:::coerceValue(v, vals$x$x$data[i, j])
    replaceData(proxy, vals$x$x$data, resetPaging = FALSE, rownames = FALSE)
  })
  
  output$download <- downloadHandler("example.csv", 
                                     content = function(file){
                                       write.csv(vals$x$x$data, file, row.names = F)
                                     },
                                     contentType = "text/csv")
  
}

shinyApp(ui = ui, server = server)