Как да регистрирате данни в таблица в приложение 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(...), за да reqубедя, че стойностите са "истини" (не NULL и т.н.). Без него, например, таблицата с дневници ще започне с ред, който има празен запис, защото е стрелял при първия изстрел. Освен това, ако потребителят въведе нещо (добавяйки ред), след което изтрие записа, това ще предотврати добавянето на празен ред. За да видите как работи това, премахнете req(input$num), след това добавете запис, след което изпразнете полето за въвеждане.

person r2evans    schedule 24.04.2020