防止从动态文件输入中多次读取文件

Prevent to read file multiple times from dynamic fileInput

我使用 lapply 在 shiny 中创建了一个动态 fileInput。当我想读取文件时,我还在观察者中使用了 lapply 。

这里使用lapply的问题是,每次我上传一个新文件时都会触发它,因此,如果上传一个新文件,它会一次又一次地读取所有文件。

这里我提供一个Hello World应用。 lapply 函数依赖于一个输入参数,为简单起见,我从中提取了该参数。

library(shiny)
ui <- fluidPage(
    titlePanel("Hello World"),
    sidebarLayout(
        sidebarPanel(),
        mainPanel(
            lapply(1:2, function(i) {
                fileInput(
                    paste0("file", i),
                    label = NULL,
                    multiple = F,
                    accept = c(
                        "text/csv",
                        "text/comma-separated-values,text/plain",
                        ".csv"
                    ),
                    buttonLabel = paste("File", i)
                )
            }),
            verbatimTextOutput("list")
        )
    )
)

server <- function(input, output) {
    r <- reactiveValues()

    observe({
        lapply(1:2, function(i) {
            file <- input[[paste0("file",i)]]
            if(is.null(file)) return()
            isolate({
                r$file[[paste(i)]] <- readr::read_csv2(file = file$datapath)
            })
        })
    })
    output$list <- renderPrint(reactiveValuesToList(r))
}

shinyApp(ui = ui, server = server)

如何替换循环或添加需求到lapply?

虽然我在评论中开始了 cache-invalidation 的道路,但我认为其他方法可能更适合您,因为您有固定数量的 fileInput字段:交换代码中的 lapplyobserve 行(加上一些其他调整)。

server <- function(input, output) {
  lapply(paste0("file", 1:2), function(nm) {
    observeEvent(input[[ nm ]], {
      req(input[[nm]], file.exists(input[[nm]]$datapath))
      readr::read_csv2(file = input[[nm]]$datapath)
    })
  })
}

解释:

  • 我正在创建反应块列表,而不是在列表上运行的反应块。这意味着 "file1" 不会对 "file2".
  • 做出反应
  • 我通过将 paste0(...) 放在 lapply 的数据中而不是函数中来简化输入名称的定义,尽管这样做同样容易
    lapply(1:2, function(i) {
      nm <- paste0("file", i)
      # ...
    })
    
  • observeEvent 外部 定义 nm 很重要,它与延迟评估和名称空间搜索顺序有关。几年前我就中招了,被郑乔纠正:不能用for循环,一定是这样的环保操作

N.B.: 这是代码的存根,远未完成:让 observeobserveEvent 读取数据然后丢弃它是错误的.. .它缺少一些东西。理想情况下,这实际上应该是 reactiveeventReactive 块,或者处理后的数据应该存储在 reactiveValuesreactiveVal 中。例如:

server <- function(input, output) {
  mydata <- lapply(paste0("file", 1:2), function(nm) {
    observeEvent(input[[ nm ]], {
      req(input[[nm]], file.exists(input[[nm]]$datapath))
      readr::read_csv2(file = input[[nm]]$datapath)
    })
  })
  observe({
    # the following are identical, the latter more declarative
    mydata[[1]]
    mydata[["file1"]]
  })
}

(关于防御性编程的另一个注意事项:您无法完全控制 readr::read_csv2 对该文件的反应......它可能会由于某种原因出错。进一步的步骤是将它包装在 tryCatch(..., error = function(e) { errfun(e); NULL; }) 其中 errfun(e) 对错误消息做了一些 有意义的事情 (记录它 and/or 在模式弹出窗口中将其提供给用户)然后 returns NULL 以便下游反应块可以使用 req(mydata[[1]]) 而不会尝试处理 NULL.

server <- function(input, output) {
  mydata <- lapply(paste0("file", 1:2), function(nm) {
    observeEvent(input[[ nm ]], {
      req(input[[nm]])
      file <- input[[nm]]
      tryCatch(
        readr::read_csv2(file = input[[nm]]$datapath),
        error = function(e) { errfun(e); NULL; })
    })
  })
  observe({
    # the following are identical, the latter more declarative
    mydata[[1]]
    mydata[["file1"]]
  })
}