Как заносить данные в таблицу в приложении 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)

Благодарю вас!


person odar    schedule 24.04.2020    source источник
comment
Где вы собираетесь хранить прошлые записи, а затем дополнять их? Я думаю, вам нужно решить эту проблему за пределами shiny, прежде чем что-то в нее вкладывать. Подсказка: rbind, dplyr::add_row или какой-либо другой механизм для добавления к (реальному) data.frame, представленному в выводе вашей таблицы. Когда вы это поймете, хорошей практикой в ​​shiny будет разделение данных на отдельные реактивные элементы, а затем использование этих элементов в других блоках.   -  person r2evans    schedule 24.04.2020


Ответы (1)


Сначала вам нужно решить, как добавить в data.frame. Сразу скажу, что рост объектов (data.frames) в R неэффективен с точки зрения памяти: каждый раз, когда вы добавляете одну строку, весь кадр копируется в память. В какой-то момент времени (пока R не выполнит некоторое управление памятью и сборку мусора) в памяти есть две полные копии фрейма, независимо от того, содержит ли он 2 строки или 20 миллионов строк. Для небольших чисел это нормально, но плохо масштабируется. Я предлагаю это решение, несмотря на это, так как считаю, что сильный текст будет использоваться в небольших масштабах и, скорее всего, не будет проблемой. Однако, если вы планируете применить это к чему-то более крупному, имейте это в виду; мало того, что это плохо для памяти, но каждая копия (с большим количеством строк) будет медленнее, чем предыдущая.

Есть два способа расширить data.frame на одну или несколько строк: rbind (у которого есть метод data.frame S3) или просто перестроить его. Две приведенные ниже функции 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(...) для require, значения которых должны быть "truthy" (не NULL , так далее). Без него, например, таблица журнала будет начинаться со строки с пустой записью, потому что она сработала при первом выстреле. Кроме того, если пользователь что-то вводит (добавляет строку), а затем удаляет запись, это предотвратит добавление пустой строки. Чтобы увидеть, как это работает, удалите req(input$num), затем добавьте запись и очистите поле ввода.

person r2evans    schedule 24.04.2020