防止从动态文件输入中多次读取文件
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
字段:交换代码中的 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)
# ...
})
- 在
observeEvent
的 外部 定义 nm
很重要,它与延迟评估和名称空间搜索顺序有关。几年前我就中招了,被郑乔纠正:不能用for
循环,一定是这样的环保操作
N.B.: 这是代码的存根,远未完成:让 observe
或 observeEvent
读取数据然后丢弃它是错误的.. .它缺少一些东西。理想情况下,这实际上应该是 reactive
或 eventReactive
块,或者处理后的数据应该存储在 reactiveValues
或 reactiveVal
中。例如:
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"]]
})
}
我使用 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
字段:交换代码中的 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) # ... })
- 在
observeEvent
的 外部 定义nm
很重要,它与延迟评估和名称空间搜索顺序有关。几年前我就中招了,被郑乔纠正:不能用for
循环,一定是这样的环保操作
N.B.: 这是代码的存根,远未完成:让 observe
或 observeEvent
读取数据然后丢弃它是错误的.. .它缺少一些东西。理想情况下,这实际上应该是 reactive
或 eventReactive
块,或者处理后的数据应该存储在 reactiveValues
或 reactiveVal
中。例如:
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"]]
})
}