Сначала вам нужно решить, как добавить в data.frame
. Сразу скажу, что рост объектов (data.frame
s) в 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)
}
)
![пример блестящего приложения](https://i.stack.imgur.com/94CG3.png)
Заметки:
Всякий раз, когда у меня есть «журнал» или аналогичная таблица, которая всегда должна иметь одинаковую структуру (количество и имена столбцов), я считаю, что лучше всего определить ее в одном месте, как правило, во вспомогательной функции. Это совершенно не обязательно, но если/когда вы когда-нибудь измените формат таблицы и пропустите одно место, которое вы вставляете в нее, вы поймете, что я имею в виду. Когда его формат дополнен в нескольких местах, при форматировании легко пропустить одно из них. В моем случае единственное место для изменения макета таблицы — внутри самой функции.
Поле ввода num
может быть слишком «отзывчивым», поскольку, как только кто-то замедляет ввод, его значение считывается и используется. Я считаю, что это может быть проблематично в пользовательских интерфейсах, поэтому я добавил shiny::debounce
: поле ввода не считается пригодным для использования, пока пользователь не перестанет вводить текст в поле на некоторое время (здесь 3 секунды). Я думаю, что 3 секунды — это слишком много для этой демонстрации, но я думаю, что смысл понятен. Если вам нужна 1 секунда, измените 3000 на 1000. Если вы хотите удалить ее, измените все num_debounced()
на input$num
и удалите строку кода debounce
.
Почти всегда (на самом деле, я не могу придумать жизнеспособного исключения) нужно отделить реактивы данных от реактивов рендеринга. То есть, я думаю, вам не следует пытаться увеличивать данные в чем-то вроде renderTable
. Лучше сформулировать данные в другом месте (в своем собственном реактивном блоке), а затем использовать эту таблицу в функции рендеринга. Это происходит по нескольким причинам, основная (для меня) заключается в том, что я обычно хочу использовать эти данные в нескольких местах, будь то для рендеринга или просто для справки (эта причина не очевидна в этом приложении, но я все равно это делаю). Другая причина в том, что это немного упрощает рендеринг реактивной блокировки.
Я использую req(...)
для req
uire, значения которых должны быть "truthy" (не NULL
, так далее). Без него, например, таблица журнала будет начинаться со строки с пустой записью, потому что она сработала при первом выстреле. Кроме того, если пользователь что-то вводит (добавляет строку), а затем удаляет запись, это предотвратит добавление пустой строки. Чтобы увидеть, как это работает, удалите req(input$num)
, затем добавьте запись и очистите поле ввода.
person
r2evans
schedule
24.04.2020
shiny
, прежде чем что-то в нее вкладывать. Подсказка:rbind
,dplyr::add_row
или какой-либо другой механизм для добавления к (реальному)data.frame
, представленному в выводе вашей таблицы. Когда вы это поймете, хорошей практикой в shiny
будет разделение данных на отдельные реактивные элементы, а затем использование этих элементов в других блоках. - person r2evans   schedule 24.04.2020