上传CSV文件到Shiny应用时如何实现更完整的数据校验?
最下方已贴出完整的最终可运行代码,对应M. Jagan提供的解决方案,实现了带健壮数据校验的用户数据上传、下载全流程,可看到try()函数如何「测试」上传操作,避免应用不必要的崩溃。
运行下方最小可复现示例(MWE)代码时,用户可上传、下载输入数据,下载保存的输入后续可通过上传功能读取。目前需要优化上传校验逻辑:实际场景中用户很容易选错文件,相比当前应用直接崩溃的表现,更希望抛出警告提示。
所有下载的CSV文件为表头为X、Y的2列矩阵,当前校验规则仅校验文件格式为CSV、包含X/Y列,正常CSV可正常上传下载,但格式错误的CSV上传时会导致应用崩溃。
具体问题
- 如何指定仅从CSV文件的对应列读取「X」和「Y」表头?当前代码会全局检索X/Y表头,已尝试
read.csv(...colClasses=c(NA, NA)))、read.csv(...)[ , 1:2]两种写法均未生效。 - 更通用的需求:是否可在上传会触发错误或崩溃时主动中断上传?类似Excel中的
if(iserror(...))逻辑。 - (可选需求,若复杂可忽略)是否可将上传警告放入
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)
内容的提问来源于stack exchange,提问作者Curious Jorge - user9788072
相关产品推荐
相关产品推荐

