【问题标题】:How to more completely validate CSV file when uploading to Shiny App?上传到 Shiny App 时如何更完整地验证 CSV 文件?
【发布时间】:2021-12-30 20:05:39
【问题描述】:

现在在最底部发布了完整的解析代码,反映了 M. Jagan 提供的解决方案。此代码提供数据上传(具有强大的数据验证)和用户输入下载的完整周期。您可以看到try() 函数如何“测试”上传以避免不必要的应用崩溃。

运行以下代码时,用户可以上传和下载输入数据。用户可以下载和保存输入,然后通过上传检索这些输入。我正在尝试改进上传验证,因为实际上用户很容易选择不正确的文件,我宁愿用警告标记而不是像现在这样让应用程序崩溃。

所有下载都保存为带有 X 和 Y 标题的 2 列矩阵。这(以及它是 CSV 的事实)是我根据以下代码进行的关键上传验证。该应用程序正确下载和上传 CSV 数据,如下图 1(下载)和图 2(上传)所示,但当它尝试上传下图 3 中格式不正确的 CSV 数据时会崩溃。

所以我的问题是:

  1. 如何指定 csv 文件中的哪些列来查找“X”和“Y”标题?目前,它到处读取 X 和 Y 标头。我试过read.csv(...colClasses=c(NA, NA))),如下图,我也试过read.csv(...)[ , 1:2],但都没有。
  2. 更一般地说,如果上传会导致错误或崩溃,有没有办法中止上传?有点像 Excel 中的 if(iserror(...))
  3. 好的,现在我正在推动它,如果这太多了,请随意忽略它。有什么方法可以将上传警告移至modalDialog?在解决上述问题后,如果我无法弄清楚,我可以随时将其移至另一个帖子。

MWE 代码:

library(dplyr)
library(shiny)
library(shinyMatrix)

interpol <- function(a, b) { # a = periods, b = matrix inputs
  c <- rep(NA, a)
  c[1] <- b[1]
  c[a] <- b[2]
  c <- approx(seq_along(c)[!is.na(c)], c[!is.na(c)], seq_along(c))$y 
  return(c)
}

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      fileInput("file1", "Optionally choose input file (csv)", accept = ".csv"),
      sliderInput('periods', 'Periods to interpolate over:', min=1, max=10, value=10),
      matrixInput("matrix1", 
                  value = matrix(c(1,5), 
                          ncol = 2, 
                          dimnames = list("Interpolate",c("X","Y"))
                  ),
                  cols =  list(names = TRUE),
                  class = "numeric"
      ),
      downloadButton("download")
    ),
    mainPanel(
      tableOutput("contents"),
      plotOutput("plot")
    )
  )
)

server <- function(input, output, session) {
  
  input_file <- reactive({
    file <- input$file1
    ext <- tools::file_ext(file$datapath)
    req(file)
    
    if(is.null(file))
      return(NULL)
    
    file_contents <- read.csv(file$datapath,header=TRUE,colClasses=c(NA, NA))
    required_columns <- c('X','Y')
    column_names <- colnames(file_contents)
    
    shiny::validate(
      need(ext == "csv", "Incorrect file type"),
      need(required_columns %in% column_names, "Incorrect file type")
    )
    
    file_contents
    
  })
  
  output$contents <- renderTable({
    input_file()
  })
  
  data <- function(){
    tibble(
      X = seq_len(input$periods),
      Y = interpol(input$periods,matrix(c(input$matrix1[1,1],input$matrix1[1,2])))
    )
  }  
  
  output$plot<-renderPlot({plot(data(),type="l",xlab="Periods (X)", ylab="Interpolated Y values")})
  
  observeEvent(input$file1,{
    updateMatrixInput(session, 
                      inputId = "matrix1", 
                      value = matrix(as.matrix(input_file()),
                                     ncol=2,
                                     dimnames = list("Interpolate",c("X","Y"))
                              )
    )
                      
  })
  
  output$download <- downloadHandler(
    filename = function() {
      paste("Inputs","csv",sep=".")
    },
    content = function(file) {
      write.csv(input$matrix1, file,row.names=FALSE)
    }
  )
}

shinyApp(ui, server)

现在解析代码:

library(dplyr)
library(shiny)
library(shinyFeedback)
library(shinyMatrix)

nms <- c("X", "Y") # < Matrix variable names (headers)

interpol <- function(a, b) { # < a = periods, b = matrix inputs
  c <- rep(NA, a)
  c[1] <- b[1]
  c[a] <- b[2]
  c <- approx(seq_along(c)[!is.na(c)], c[!is.na(c)], seq_along(c))$y 
  return(c)
}

ui <- fluidPage(
  useShinyFeedback(),
  sidebarLayout(
    sidebarPanel(
      fileInput("file", "Optionally choose input file (csv)", accept = ".csv"),
      sliderInput('periods', 'Periods to interpolate over:', min=1, max=10, value=10),
      matrixInput("matrix1", 
                  "Values to interpolate:",
                  value = matrix(c(1,5),ncol = 2,dimnames = list(NULL,nms)),
                  cols =  list(names = TRUE),
                  rows = list(names = FALSE),
                  class = "numeric"
      ),
      downloadButton("download")
    ),
    mainPanel(
      tableOutput("contents"),
      plotOutput("plot"),
      verbatimTextOutput("verb")
    )
  )
)

server <- function(input, output, session) {
  uploadData <- reactive({
    req(input$file)
    validate(need(identical(tools::file_ext(input$file$datapath),"csv"),"Invalid extension"))
    try(read.csv(input$file$datapath, header = TRUE))
  })
  
  observeEvent(uploadData(), {
    if(is.data.frame(uploadData()) && 
       all(nms %in% names(uploadData())) && 
       all(vapply(uploadData()[nms],is.numeric,NA))){
          updateMatrixInput(session,"matrix1",as.matrix(uploadData()[nms]))
          hideFeedback("file")
        } 
    else {
      showFeedbackWarning("file", "Invalid upload.")
    }
  })

  data <- function(){
    tibble(
      X = seq_len(input$periods),
      Y = interpol(input$periods,matrix(c(input$matrix1[1,1],input$matrix1[1,2])))
    )
  }  
  
  output$plot<-renderPlot({plot(data(),type="l",xlab="Periods (X)", ylab="Interpolated Y values")})
  output$verb <- renderPrint(class(uploadData()))  
  
  output$download <- downloadHandler(
    filename = function() {
      paste("Inputs","csv",sep=".")
    },
    content = function(file) {
      write.csv(input$matrix1, file,row.names=FALSE)
    }
  )
}

shinyApp(ui, server)

【问题讨论】:

  • 对于 (2),您可以执行 file_contents &lt;- try(read.csv(...)) 并验证 is.data.frame(file_contents)TRUE。见?try
  • 对于(1),你试过file_contents[c("X", "Y")]吗?
  • 好吧,让我研究一下 try()。基本上,如果上传和相关的连接对象(矩阵更新、绘图)导致发生一些有趣的事情,我希望忽略上传并继续前进;用户可以尝试再次上传或手动输入到矩阵中。您对 (1) 的最后评论,那会去哪里?我在几个地方都试过了,我得到了错误。

标签: r validation shiny upload


【解决方案1】:

以下内容应满足您的需求

shiny::validate(
  need(ext == "csv", "Incorrect file type"),
  need(required_columns %in% column_names, "Incorrect file type"),
  need(sum(!column_names %in% required_columns)==0, "Incorrect columns in file")
)

更新:如果您可以修改您的支票,您可以执行以下操作。

shiny::validate(
  need(ext == "csv", "Incorrect file type"),
  need(sum(required_columns %in% column_names)==2 & sum(!column_names %in% required_columns)==0, "Incorrect file type")
)

【讨论】:

  • 感谢 YBS,这适用于我发布的示例,其中 X 和 Y 位于 csv 的第 2 列和第 3 列,而不是它们应该位于的 csv 的前 2 列.但是一般的错误检查怎么样,如果出现错误(应用程序崩溃),上传(以及绘制和更新矩阵的相关反应)会被简单地拒绝?在这种错误情况下,应用程序不会崩溃,什么也不会发生,用户只需输入矩阵或用户搜索要上传的正确文件。
  • 上述验证不起作用?您可以结合第 2 次和第 3 次检查。查看更新的答案。
  • 发布的更新仍然会导致崩溃,当尝试上传错误的 csv 时,就像我在上面新发布的图像 4 中显示的那样(我编辑了我的原始帖子以显示图像 4)。在为图像 4 模拟错误的用户上传时,我收到消息“警告:read.table 中的错误:列多于列名 [没有可用的堆栈跟踪]”。虽然此更新确实适用于图像 3 错误!我想知道更通用的方法是否更好,例如使用 try() 来简单地忽略会导致崩溃的上传。有无数的用户错误可能性,无法在 validate(need(...)) 中全部捕获
  • 对我来说,它工作正常,因为当您选择一个只有 X 变量的文件时,它会显示“不正确的文件类型”。语句 sum(required_columns %in% column_names)==2 正在检查您是否有 X 和 Y 变量,而语句 sum(!column_names %in% required_columns)==0 正在检查您的文件中是否有任何其他变量。如果任一语句失败,则会显示“文件类型不正确”。
【解决方案2】:

我创建了您的应用程序的最小版本(没有插值或下载),我认为可以解决 (1) 和 (2) 问题,并且您希望在发生无效上传的情况下保留现有矩阵和绘图.您应该能够通过修改此框架来重新构建您的应用程序,但在此之前,您应该尝试了解此应用程序的工作原理。

请注意,我添加了对包shinyFeedback 的依赖项,它将警告消息放置在相应的输入面板附近。如果有问题请告诉我...

library("shiny")
library("shinyFeedback")
library("shinyMatrix")

## Your variable names
nms <- c("X", "Y")

ui <- fluidPage(
  useShinyFeedback(),
  sidebarLayout(
    sidebarPanel(
      fileInput("file", label = "CSV file", accept = ".csv"),
      matrixInput("mat", label = "Matrix", value = matrix(rnorm(12L), 6L, 2L, dimnames = list(NULL, nms)), class = "numeric", rows = list(names = FALSE))
    ),
    mainPanel(
      plotOutput("plot"),
      verbatimTextOutput("verb")
    )
  )
)

server <- function(input, output, session) {
  rawdata <- reactive({
    req(input$file)
    try(read.csv(input$file$datapath, header = TRUE))
  })

  observeEvent(rawdata(), {
    ## If 'rawdata()' is a data frame with numeric variables named 'nms'
    if (is.data.frame(rawdata()) && all(nms %in% names(rawdata())) && all(vapply(rawdata()[nms], is.numeric, NA))) {
      ## Then update matrix by extracting those variables, ignoring the rest (if any)
      updateMatrixInput(session, "mat", as.matrix(rawdata()[nms]))
      ## And suppress warning if visible
      hideFeedback("file")
    } else {
      ## Otherwise show warning
      showFeedbackWarning("file", "Invalid upload.")
    }
  })
  
  ## Plots matrix rows as points
  output$plot <- renderPlot(plot(input$mat))
  ## Prints "try-error" if 'read.csv' threw error, "data.frame" otherwise
  output$verb <- renderPrint(class(rawdata()))  
}

shinyApp(ui, server)

这里是您可以用来创建测试文件的代码。每一个都测试应用的不同行为。

## OK
cat("X,Y,Z\na,1,3,5\nb,2,4,6\n", file = "test1.csv")
## OK: file contents matter, not file extension
cat("X,Y,Z\na,1,3,5\nb,2,4,6\n", file = "test2.txt")
## Missing 'X'
cat("W,Y,Z\na,1,3,5\nb,2,4,6\n", file = "test3.csv")
## 'X' is not numeric
cat("X,Y,Z\na,hello,3,5\nb,world,4,6\n", file = "test4.csv")
## Not a valid CSV file
cat("read.csv\nwill,not,like,this,file\n", file = "test5.csv")

【讨论】:

    猜你喜欢
    • 2023-04-10
    • 1970-01-01
    • 2019-05-02
    • 2018-01-02
    • 1970-01-01
    • 2017-02-03
    • 2012-04-03
    • 1970-01-01
    • 2010-12-13
    相关资源
    最近更新 更多