R Shiny模块返回值应为data.frame却为character问题排查求助
问题描述
- 开发由
Import.Excel.Data.UI和Import.Excel.Data.Server组成的Shiny模块,接收用户输入的Excel文件路径、工作表名称、数据范围、勾选框状态四个字段,预期输出为dataframe类型,每一行对应一组配置参数。 - 主应用
Import.Excel.Data.App调用模块输出,计划将返回的dataframe逐行传入readXLS函数读取数据,当前接收模块输出的代码如下:
ABC <- Import.Excel.Data.Server("File1")
- 问题表现:模块内部已将结果转为dataframe类型,但主应用中调用
class(ABC())返回值为character类型,而非预期的data.frame。主应用中调试用代码如下:
output$ABC.class <- renderText(class(ABC())) output$ABC.matrix <- renderPrint(ABC())
原始代码
模块代码
library(shiny) Import.Excel.Data.UI <- function(id){ ns <- NS(id) tagList( actionButton(ns("AddExcelDataButton"), label = "Click Here to Add Excel Data"), verbatimTextOutput(ns("XLmatrix")) ) } Import.Excel.Data.Server <- function(id){ moduleServer(id, function(input, output, session){ ns <- session$ns observeEvent(eventExpr = input$AddExcelDataButton, insertUI(selector=paste0("#",ns("AddExcelDataButton")), multiple=TRUE, where = "afterEnd", ui = tags$hr( tags$div(fileInput(inputId = ns(paste0("ExcelFile",input$AddExcelDataButton)), label = paste0("Path for File",input$AddExcelDataButton), multiple = FALSE), style = "display:inline-block; vertical-align:top" ), tags$div(textInput(inputId = ns(paste0("ExcelSheetName",input$AddExcelDataButton)), label = paste0("Excel Sheet Name",input$AddExcelDataButton), value = "Data"), style = "display:inline-block; vertical-align:top" ), tags$div(textInput(inputId = ns(paste0("ExcelSheetRange",input$AddExcelDataButton)), label = paste0("Excel Sheet Range", input$AddExcelDataButton), value = "C6:AL10000"), style = "display:inline-block; vertical-align:top" ), tags$div(checkboxInput(inputId = ns(paste0("ExcelFileCheck",input$AddExcelDataButton)), label = paste0("Check to Use File", input$AddExcelDataButton), value = TRUE), style = "display:inline-block; vertical-align:center" ) ) ) ) XLdata <- reactive({ for (i in 1: input$AddExcelDataButton) { temp.filepath <- input[[paste0("ExcelFile",i)]]$datapath temp.sheetname <- input[[paste0("ExcelSheetName",i)]] temp.sheetrange <- input[[paste0("ExcelSheetRange",i)]] temp.filecheck <- input[[paste0("ExcelFileCheck",i)]] row.temp <- cbind(temp.filepath, temp.sheetname, temp.sheetrange, temp.filecheck) ifelse(i<=1, { XLdata.matrix <- row.temp }, { XLdata.matrix <- rbind(XLdata.matrix, row.temp) } ) } XLdata.matrix <- as.data.frame(XLdata.matrix) XLdata.matrix }) output$XLmatrix <- renderPrint(XLdata()) }) }
主应用代码
Import.Excel.Data.App <- function(){ ui <- fluidPage( Import.Excel.Data.UI("File1"), textOutput("ABC.class"), verbatimTextOutput("ABC.matrix") ) server <- function(input, output, session){ ABC <- Import.Excel.Data.Server("File1") output$ABC.class <- renderText(class(ABC())) output$ABC.matrix <- renderPrint(ABC()) } shinyApp(ui, server) } Import.Excel.Data.App()
问题原因与修复方案
核心原因
- 模块服务端没有显式返回需要的reactive对象:Shiny的moduleServer默认返回服务端逻辑中最后一行代码的执行结果,原始代码最后一行是给output赋值的语句,返回值为character类型,自然不是预期的dataframe。
- reactive逻辑中误用了向量化函数
ifelse做分支判断,会导致数据类型异常,且没有做非空校验,容易触发报错。
修复步骤
仅需修改Import.Excel.Data.Server部分代码:
- 在模块服务端的最后一行(
output$XLmatrix <- renderPrint(XLdata())之后)添加return(XLdata),显式返回封装了dataframe的reactive对象。 - 调整
XLdatareactive的逻辑,替换ifelse为普通分支判断,增加非空校验避免报错,修改后代码如下:
XLdata <- reactive({ req(input$AddExcelDataButton) XLdata.matrix <- NULL for (i in 1: input$AddExcelDataButton) { req(input[[paste0("ExcelFile",i)]]) # 跳过未上传文件的配置行 temp.filepath <- input[[paste0("ExcelFile",i)]]$datapath temp.sheetname <- input[[paste0("ExcelSheetName",i)]] temp.sheetrange <- input[[paste0("ExcelSheetRange",i)]] temp.filecheck <- input[[paste0("ExcelFileCheck",i)]] row.temp <- data.frame( filepath = temp.filepath, sheetname = temp.sheetname, range = temp.sheetrange, enable = temp.filecheck, stringsAsFactors = FALSE ) if (is.null(XLdata.matrix)) { XLdata.matrix <- row.temp } else { XLdata.matrix <- rbind(XLdata.matrix, row.temp) } } XLdata.matrix })
修复完成后,主应用中ABC()即可返回预期的dataframe对象,直接传入readXLS相关函数即可使用。
内容的提问来源于stack exchange,提问作者K.RAC
相关产品推荐
相关产品推荐

