В настоящее время у меня есть приложение Shiny с 3 меню (дополнительные будут добавлены, когда ошибки будут исправлены) .
Я нашел в Интернете примеры подхода к фильтрации с помощью меню сверху вниз. Это означает, что пользователь должен выбрать из первого меню, затем из второго меню и так далее, но по порядку. Если они сначала выбирают из 2-го меню, тогда оно не фильтрует первое меню, а только те, что под ним, и, очевидно, это проблема.
Я хочу, чтобы мои пользователи могли переходить к меню в любом порядке и фильтровать их.
В моем примере есть 3 меню, и я пытаюсь сделать следующее: если observeEvent в меню любой (пользователь делает выбор из любого меню), то:
updateSelectInput для любых меню, для которых еще не выбран входЭто обеспечит актуальность меню в соответствии с тем, что на самом деле содержится в данных, и гарантирует, что пользователь не перейдет к тому, чего на самом деле не существует в данных. Кроме того, Примечание, что шаг № 2 очень важен - обновлять только меню без выбора, у меня были проблемы с этим, потому что, если я просто обновляю все другие меню, он очищает выбранный пользователем ввод, что по-прежнему является неправильным поведением. Я знаю, что мне нужно сделать, но я еще не смог это сделать, поэтому помощь приветствуется.
Обновлять
Я обновил свой код, чтобы он работал с одним ответом, опубликованным ниже, но он по-прежнему работает некорректно.
Теперь он фильтрует меню, однако, как только подмножество было создано, оно не позволяет ему «фильтровать» резервную копию.
Я имею в виду, что если я выберу значение 3 из первого меню TreeNumber, то последнее меню отфильтруется только до значения 300 - это хорошо. НО, если я затем вернусь в первое меню и также выберу значение 4, я ожидаю, что в меню Circumference теперь будут отображаться значения: 300 и 400, однако оно по-прежнему показывает только значение 300.
Обновленный код:
d <- data.frame("TreeNumber" = c(replicate(7, 1), replicate(7, 2),
replicate(7, 3), replicate(7, 4)),
"TreeAge" = c(1:28),
"Circumference" = c(replicate(7, 100), replicate(7, 200),
replicate(7, 300), replicate(7, 400)))
col_names <- names(d)
# TODO - change these to: "Tree Number", "Tree Age", "Circumference"
user_friendly_names <- c('TreeNumber', 'TreeAge', 'Circumference')
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
h3("Filters:"),
uiOutput("filters"),
# Plot button
fluidRow(column(2, align = "right",
actionButton("plot_graph_button", "Plot")))
),
mainPanel(tableOutput("summary"))
)
)
server <- function(input, output, session) {
#### Create the filter lists for UI ####
output$filters <- renderUI({
if (is.null(col_names)) return(NULL)
lapply(1:length(col_names), function(i) {
col <- paste0(col_names[i])
alias <- user_friendly_names[i]
# Populate input with unique values from column
pickerInput(inputId = alias, label = paste(alias,':'),
choices = unique(d[[col]]), multiple = T)
})
})
# lapply(X = vars, FUN = function(x) {
# vals <- sort(unique(data[[x]]))
# updatePickerInput(session = session, inputId = x, choices = vals)
# })
my_filter <- function(data, var) {
# TODO - Need to convert from user_friendly_names --> col_names in here
if (length(input[[var]]) == 0) return(data)
data %>% subset(data[[var]] %in% input[[var]])
}
subsettedData <- reactive({
d %>% my_filter("TreeNumber") %>% my_filter("TreeAge") %>%
my_filter("Circumference")
# TODO - get into for loop versus hard coding this step:
# for(z in 1:length(col_names)){
# d %>% my_filter(col_names[z])
# }
})
observeEvent(subsettedData(), {
lapply(col_names, function(var) {
selections <- unique(subsettedData()[[var]])
if (length(input[[var]]) == 0)
updatePickerInput(session = session, inputId = var, choices = selections)
})
})
observeEvent(input$plot_graph_button, {
for (j in seq_along(d)) {
updateSelectInput(session = session, inputId = user_friendly_names[j],
choices = c("All", unique(d[[j]])), selected = "All")
}
})
output$summary <- renderTable({
# Do not show a plot when the page first loads
# Wait until the user clicks "Plot" button
if (input$plot_graph_button == 0){
return()
}
# Update code below everytime the "Plot" button is clicked
input$plot_graph_button
isolate({
# Fresh copy of the full data set every time "Plot" button is clicked
d <- copy(Orange)
# Filter data based on UI
for(f in 1:length(col_names)){
if (eval(parse(text = paste0('is.null(input$',user_friendly_names[f],')')))){
# Default to "All" - do not filter
print("All")
}else{
d <- d[d[[col_names[f]]] ==
unlist(eval(parse(text =
paste0('input$',user_friendly_names[f])))), ]
}
}
final_summary_table <<- d
})
})
}
shinyApp(ui = ui, server = server)
@ r2evans copy - это функция. Просто делаю копии данных, чтобы убедиться, что я ничего не переписываю. Я предполагаю, что в решении будет использоваться update____, да.
Копия @ r2evans находится в пространстве имен data.table для создания глубокой копии объекта data.table. Его также можно применить к data.frames, и в этом случае он действует как as.data.table.
Моя проблема ... Я сделал обычный ??copy, и он не вернул data.table::copy (и я не слишком хорошо знаком с data.table), но я вижу (вручную), что он там. Спасибо. (Конечно, теперь ??copyявляется возвращает эту функцию, поэтому мне нужно больше - или меньше - кофеина.)





Вот приложение, которое применяет фильтрацию на основе всех входных данных. Я не уверен, насколько интуитивно понятно, чтобы выбрать «все» в selectInput с multiple = TRUE. Возможно, лучше было бы вместо этого добавить кнопку сброса для каждого выбора.
Я заменил набор данных Orange на tips, чтобы получить больше факторных переменных. Кроме того, я не использовал data.table в этом примере, поскольку он не имеет отношения к вашей проблеме.
library(shiny)
library(dplyr)
data(tips, package = "reshape2")
filter_vars <- c("sex", "smoker", "day", "time")
ui <- fluidPage(
lapply(filter_vars, function(var) {
selectInput(var, var, unique(tips[[var]]), multiple = TRUE)
}),
tableOutput("table")
)
server <- function(input, output, session) {
my_filter <- function(data, var) {
if (length(input[[var]]) == 0) return(data)
data %>% subset(data[[var]] %in% input[[var]])
}
subsettedData <- reactive({
tips %>% my_filter("sex") %>% my_filter("smoker") %>%
my_filter("day") %>% my_filter("time")
})
observeEvent(subsettedData(), {
lapply(filter_vars, function(var) {
selections <- unique(subsettedData()[[var]])
if (length(input[[var]]) == 0)
updateSelectInput(session, var, choices = selections)
})
})
output$table <- renderTable({ subsettedData() })
}
shinyApp(ui, server)
Я использовал свой пример кода только для упрощения, но в моем более сложном коде я отказался от All, потому что это не лучший способ обрабатывать меню, как вы указали.
Ваш код создает меню ввода текста, мой - раскрывающееся меню. Этот код недостаточно похож на мой пример, чтобы он работал так, как мне нужно. В вашем примере, когда что-то удаляется, оно исчезает, и это недостаток.
См. Мое отредактированное решение. Для меня было непонятно, должна ли подустановка быть обратимой.
Для этого раздела: subsettedData <- reactive({ tips %>% my_filter("sex") %>% my_filter("smoker") %>% my_filter("day") %>% my_filter("time") }) есть ли способ избежать жесткого кодирования каждой категории?
Да, вы можете просто использовать цикл for для имен переменных и каждый раз заменять данные отфильтрованными.
Также обратите внимание на shinyWidgets::pickerGroupServer для готового решения для этого
Я обновил вопрос, включив в него ваш ответ. Он фильтрует меню, но не отменяет фильтр. Это означает, что после фильтрации набора данных он не может вернуться вверх, если были сделаны другие выборки. (Я объяснил это более подробно в редактировании моего вопроса выше)
(Где определяется
copy?) Можно ли это сделать сshiny::updateSelectizeInput?