【问题标题】:Prevent to read file multiple times from dynamic fileInput防止从动态文件输入中多次读取文件
【发布时间】:2019-12-14 02:02:09
【问题描述】:

我使用 lapply 在闪亮中创建了一个动态文件输入。当我想读取文件时,我还在观察者中使用了 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?

【问题讨论】:

  • 代码示例可能有点太小了:observe 只是读取数据(并立即丢弃它)并没有显示您正在尝试做什么。但是一个想法:是否有可能以相同的名称但不同的数据重新上传文件?如果是,那么您有 cache-invalidation 的问题:您可以选择不重新读取您之前看到的文件,但如果上传了一个新的(不同的数据)同名文件,你永远看不到它。
  • 如果你确定同名总是相同的(或愿意接受结果),那么我建议你创建一个rv &lt;- reactiveValues(),名称为文件名;在您的observe 中,检查是否已读取文件名(每个文件的file$datapath %in% names(rv)),如果没有存储表(rv[[ file$datapath ]] &lt;- readr::read_csv2(...))。
  • 感谢您的快速回复!我的应用程序中没有直接丢弃数据。我将 reactiveValues 添加到文件部分和输出以指示处理和保存方案。

标签: r file-io shiny lapply observers


【解决方案1】:

虽然我从 cmets 中的 cache-invalidation 开始,但我认为其他方法可能更适合您,因为您有固定数量的 fileInput 字段:交换 lapply和代码中的 observe 行(以及其他一些调整)。

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)
      # ...
    })
    
  • nm 定义在observeEvent 之外 很重要,这与延迟评估和命名空间搜索顺序有关。几年前我遇到了这个问题,被Joe Cheng纠正了:你不能使用for循环,它必须是这样的环保操作。

注意:这是一段代码,远未完成:让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)对错误消息进行有意义的操作(记录它和/或在模式弹出窗口中将其提供给用户)然后返回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"]]
  })
}

【讨论】:

  • lapply 部分现在工作得很好,谢谢,一个文件不依赖于下一个文件。虽然如果我错过了一部分很抱歉,但是保存 lapply 使我的应用程序崩溃了 Error in &lt;observer&gt;: attempt to apply non-function
  • 哦,对不起,我没有测试就输入了。 reactiveVal 需要括号,reactiveValues 不需要。我正在从我的答案中删除它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-06
  • 1970-01-01
  • 1970-01-01
  • 2021-10-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多