首页 > 解决方案 > 如何在 Shiny 应用程序中将数据记录到表中

问题描述

我有 numericInput 和 tableOutput 小部件。目前,在框中键入的任何数字都打印在同一行的表格中。我想做的是在新行上打印每个新条目,以便表格自动扩展(变高)并记录所有条目。我不一定需要使用闪亮包中的表格。例如,对于 DT 或 Formattable 包中的表,解决方案会有所不同吗?代表是:

library(shiny)
ui <- fluidPage(
    sidebarLayout(
        sidebarPanel(
            numericInput(inputId = "num", label = h3("Enter value:"), value = "")
        ),
        mainPanel(
            tableOutput("table")
        )
    )
)
server <- function(input, output) {
    output$table <- renderTable({
        paste("Value is:", input$num, sep = "\n")
    },
    bordered = TRUE)
}
shinyApp(ui = ui, server = server)

谢谢!

标签: rshiny

解决方案


您首先需要解决如何添加到data.frame. 我将预先说明 R 中不断增长的对象 ( data.frames) 在内存方面效率低下:每次添加一行时,整个帧都会复制到内存中。有一段时间(直到 R 进行一些内存管理和垃圾收集),内存中有两个完整的帧副本,无论是 2 行还是 2000 万行。对于小数字,这很好,但扩展性很差。尽管如此,我还是建议使用此解决方案,因为我相信您使用的强文本将是小规模的,并且可能不会成为问题。但是,如果您打算将其应用于其他更大的东西,请记住这一点;不仅内存不好,而且每个副本(有很多行)都会比上一个慢。

有两种方法可以将 a 扩展data.frame一行或多行:(rbind具有data.frameS3 方法)或只是重建它。下面的两个mylog函数演示了这两个:给定先前的数据(或NULL第一次)和一个条目,它返回一个增强的帧。

mylog1 <- function(dat, entry) {
  if (is.null(dat)) dat <- data.frame(timestamp = character(0), entry = integer(0))
  rbind.data.frame(dat, data.frame(
    timestamp = rep(format(Sys.time(), format = "%H:%M:%S"), length(entry)),
    entry = entry,
    stringsAsFactors = FALSE
  ), stringsAsFactors = FALSE)
}
mylog2 <- function(dat, entry) {
  if (is.null(dat)) dat <- data.frame(timestamp = character(0), entry = integer(0))
  data.frame(
    timestamp = c(dat$timestamp, rep(format(Sys.time(), format = "%H:%M:%S"), length(entry))),
    entry = c(dat$entry, entry),
    stringsAsFactors = FALSE
  )
}

library(shiny)
shinyApp(
  ui = fluidPage(
    sidebarLayout(
      sidebarPanel(
        numericInput(inputId = "num", label = h3("Enter value:"), value = "")
      ),
      mainPanel(
        tableOutput("table")
      )
    )
  ),
  server = function(input, output) {
    mydata <- reactiveVal( mylog1(NULL, integer(0)) )
    num_debounced <- debounce(reactive(input$num), 3000)
    observeEvent(num_debounced(), {
      req(input$num)
      dat <- mylog1(mydata(), num_debounced())
      mydata(dat)
    })
    output$table <- renderTable({
      req(mydata())
    },
    bordered = TRUE)
  }
)

闪亮的应用程序示例

笔记:

  1. 每当我有一个应该始终具有相同结构(列的数量和名称)的“日志”或类似表时,我发现最好将它定义在一个地方,通常是一个辅助函数。这绝对不是必需的,但是如果/当您更改表格的格式并错过插入其中的一个位置时,您就会明白我的意思。当它的格式在多个地方增加时,格式化时很容易漏掉一个。就我而言,更改表格布局的唯一地方是函数本身。

  2. 输入num字段可能过于“响应”,因为一旦有人放慢打字速度,它的值就会被读取和使用。我发现这在用户界面中可能会出现问题,所以我补充说shiny::debounce:在用户停止输入字段一段时间(此处为 3 秒)之前,输入字段不被视为可用。我认为 3 秒对于这个演示来说太长了,但我认为它明白了这一点。如果要 1 秒,将 3000 更改为 1000。如果要删除它,请将其全部更改num_debounced()input$num并删除该debounce行代码。

  3. 几乎总是(实际上,我想不出一个可行的例外)将数据反应式与渲染反应式分开。也就是说,我认为您不应该尝试在诸如renderTable. 最好在其他地方(在它自己的反应块中)制定数据,然后在渲染函数中使用该表。这有几个原因,主要(对我而言)是我通常想在多个地方使用该数据,无论是用于渲染还是仅用于引用(这个原因在这个应用程序中并不明显,但我还是这样做了)。另一个原因是稍微简化了渲染反应锁。

  4. req(...)习惯req认为价值观是“真实的”(不是NULL等)。例如,如果没有它,日志表将从具有空条目的行开始,因为它在第一次射击时触发。此外,如果用户输入某些内容(添加一行)然后删除该条目,这将防止添加空行。要查看它是如何工作的,请删除req(input$num)然后添加一个条目,然后清空输入字段。


推荐阅读