当输入相互依赖时,如何防止 运行 输出两次?
How to prevent output from running twice when inputs are inter-dependent?
我正在开发一个基于 R Shiny 的应用程序。
我想让我的输入与可用数据保持一致,因此我更新了 selectInput 中的选定值。
当我更改输入 1 中的选定值时,输入 2 的值将更新,然后数据将更新(仅一次)。好的
但是,如果我更改输入 2 中的选定值,则数据会更新,然后输入 1 的值会更新,然后数据会再次更新。
查看打印两次的"check latest_value"。
最初我使用 renderUI 而不是 updateSelectInput,但在初始化时,数据被计算了两次。
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type)),
uiOutput('plant_ui'),
DTOutput('plot')
),
server = function(input, output) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
latest_plant_value=reactive({
if(is.null(input$plant))data()$Plant[1]
else input$plant
})
output$plant_ui=renderUI({
sub_data=data()
selectInput(inputId = 'plant',"filtre par plant",choices = unique(sub_data$Plant),
selected=latest_plant_value())
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
这就是为什么我决定使用基于此的 updateSelectInput 但是代码的顺序结构使得当我更改输入 2 值时数据要计算两次。
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
print("update type changed")
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
print("update plant changed")
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
像这样的修复在那种情况下不起作用,因为我不想达到同样的目的three interdependent selectInput in R/Shiny application
我希望每个输入的默认选择值保持一致,以便过滤器 returns 至少有 1 个值。这是我更改的任何输入。
解决这个问题的一种方法是创建一个 reactiveVal
告诉应用程序正在进行更新操作,并要求 data
等到该标志 returns 运行ning.
前为假
我已经在你的第二个闪亮应用中添加了 5 行:
至server()
:
# Create update in progress flag
updating_type_inprogress <- reactiveVal(FALSE)
至observeEvent(input$type ...
:
# When type is changed, set flag to TRUE
updating_type_inprogress(TRUE)
至observeEvent(input$plant ...
:
# Once this function has run, the updating operation is done
updating_type_inprogress(FALSE)
至data()
:
# Stops updating data() if the in-progress flag is TRUE
req(!updating_type_inprogress())
至renderDT()
:
# Stops updating renderDT() if the in-progress flag is TRUE
# this is probably optional unless there's resource-intensive code
# that doesn't depend on changes in data()
req(!updating_type_inprogress())
完整代码如下:
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
req(!updating_type_inprogress())
print(input$type)
print(input$plant)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
updating_type_inprogress(TRUE)
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
updating_type_inprogress(FALSE)
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
updating_type_inprogress <- reactiveVal(FALSE)
output$plot <- renderDT({
req(!updating_type_inprogress())
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
如您所见,当您更改 input$type
时,data()
和 renderDT()
仅使用 运行 一次正确更新的值:
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
[1] "check latest_value"
[1] "Mississippi"
[1] "Mn1"
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
有趣的问题,但不容易解决!有趣的是,您所要求的并不是您所需要的。观察:
- 如果用户选择 Qn2 而 Input1 为 "Mississippi",您首先将 Input1 设置为 Quebec 然后 hard set Input2 on Qn1,改变用户的选择。这很糟糕。
一旦两个输入中的任何一个发生变化,- Datatable 总是更新,因此 table.
的多次重新计算
因此解决方案是双重的:
- 不要覆盖用户的选择,例如Qc2 到 Qc1。我为此使用了 if 条件。
- 安装 watchguard 以仅更新
datatable 当它的内容实际改变时。我用 reactiveVal() 来做到这一点,我只在两个输入的选择有效时更新(即当结果集大于 0 时)。
查看下面的结果。观察控制台输出以观察决策。
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du plant',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
latest_data <- reactiveVal(my_data)
observe({
result <- my_data %>% filter(Type %in% input$type, Plant %in% input$plant)
if(nrow(result) > 0){
latest_data(result)
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update the dataframe because the choice was not valid.\n")
}
})
observeEvent(input$type,{
if(! input$plant %in% my_data$Plant[my_data$Type == input$type]){
old <- input$plant
new <- my_data %>% filter(Type %in% input$type) %>% slice(1) %>% pull(Plant) %>% as.character()
updateSelectInput(session, "plant", selected = new)
cat(format(Sys.time(), "%H:%M:%S"), "Updated input$plant from", old, "to", new, "so that it represents a valid choice for", input$type, "\n")
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update input$plant", input$plant, "because it is a valid choice for", input$type, "already\n")
}
})
observeEvent(input$plant,{
updateSelectInput(session, "type",
selected = my_data %>% filter(Plant %in% input$plant) %>% slice(1) %>% pull(Type))
})
output$plot <- renderDT({
cat(format(Sys.time(), "%H:%M:%S"), "updating datatable to only include", isolate(input$plant), "and", isolate(input$type), "\n\n")
latest_data()
datatable(latest_data())
})
}
)
我正在开发一个基于 R Shiny 的应用程序。 我想让我的输入与可用数据保持一致,因此我更新了 selectInput 中的选定值。 当我更改输入 1 中的选定值时,输入 2 的值将更新,然后数据将更新(仅一次)。好的 但是,如果我更改输入 2 中的选定值,则数据会更新,然后输入 1 的值会更新,然后数据会再次更新。 查看打印两次的"check latest_value"。
最初我使用 renderUI 而不是 updateSelectInput,但在初始化时,数据被计算了两次。
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type)),
uiOutput('plant_ui'),
DTOutput('plot')
),
server = function(input, output) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
latest_plant_value=reactive({
if(is.null(input$plant))data()$Plant[1]
else input$plant
})
output$plant_ui=renderUI({
sub_data=data()
selectInput(inputId = 'plant',"filtre par plant",choices = unique(sub_data$Plant),
selected=latest_plant_value())
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
这就是为什么我决定使用基于此的 updateSelectInput
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
# req(input$type)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
print("update type changed")
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
print("update plant changed")
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
output$plot <- renderDT({
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
像这样的修复在那种情况下不起作用,因为我不想达到同样的目的three interdependent selectInput in R/Shiny application
我希望每个输入的默认选择值保持一致,以便过滤器 returns 至少有 1 个值。这是我更改的任何输入。
解决这个问题的一种方法是创建一个 reactiveVal
告诉应用程序正在进行更新操作,并要求 data
等到该标志 returns 运行ning.
我已经在你的第二个闪亮应用中添加了 5 行:
至server()
:
# Create update in progress flag
updating_type_inprogress <- reactiveVal(FALSE)
至observeEvent(input$type ...
:
# When type is changed, set flag to TRUE
updating_type_inprogress(TRUE)
至observeEvent(input$plant ...
:
# Once this function has run, the updating operation is done
updating_type_inprogress(FALSE)
至data()
:
# Stops updating data() if the in-progress flag is TRUE
req(!updating_type_inprogress())
至renderDT()
:
# Stops updating renderDT() if the in-progress flag is TRUE
# this is probably optional unless there's resource-intensive code
# that doesn't depend on changes in data()
req(!updating_type_inprogress())
完整代码如下:
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
# Running a Shiny app object
app <- shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du type',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
data=reactive({
req(!updating_type_inprogress())
print(input$type)
print(input$plant)
my_data_temp=my_data
if(length(input$type)>0){
my_data_temp=my_data_temp%>%filter(Type%in%input$type)
}
if(length(input$plant)>0){
my_data_temp=my_data_temp%>%filter(Plant%in%input$plant)
}
my_data_temp
})
observeEvent(input$type,{
updating_type_inprogress(TRUE)
updateSelectInput(session, "plant",
selected = unique(my_data%>%filter(Type%in%input$type)%>%.$Plant)[1])
})
observeEvent(input$plant,{
updating_type_inprogress(FALSE)
updateSelectInput(session, "type",
selected = unique(my_data%>%filter(Plant%in%input$plant)%>%.$Type)[1])
})
updating_type_inprogress <- reactiveVal(FALSE)
output$plot <- renderDT({
req(!updating_type_inprogress())
print("check latest_value")
datatable(data()) })
}
)
runApp(app)
如您所见,当您更改 input$type
时,data()
和 renderDT()
仅使用 运行 一次正确更新的值:
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
[1] "check latest_value"
[1] "Mississippi"
[1] "Mn1"
[1] "check latest_value"
[1] "Quebec"
[1] "Qn1"
有趣的问题,但不容易解决!有趣的是,您所要求的并不是您所需要的。观察:
- 如果用户选择 Qn2 而 Input1 为 "Mississippi",您首先将 Input1 设置为 Quebec 然后 hard set Input2 on Qn1,改变用户的选择。这很糟糕。 一旦两个输入中的任何一个发生变化,
- Datatable 总是更新,因此 table. 的多次重新计算
因此解决方案是双重的:
- 不要覆盖用户的选择,例如Qc2 到 Qc1。我为此使用了 if 条件。
- 安装 watchguard 以仅更新 datatable 当它的内容实际改变时。我用 reactiveVal() 来做到这一点,我只在两个输入的选择有效时更新(即当结果集大于 0 时)。
查看下面的结果。观察控制台输出以观察决策。
library(shiny)
library(DT)
library(dplyr)
my_data=data.frame(CO2)
shinyApp(
ui = bootstrapPage(
selectInput('type','Choix du type',choices = unique(my_data$Type),selected=my_data$Type[1]),
selectInput('plant','Choix du plant',choices = unique(my_data$Plant),selected=my_data$Plant[1]),
DTOutput('plot')
),
server = function(input, output,session) {
latest_data <- reactiveVal(my_data)
observe({
result <- my_data %>% filter(Type %in% input$type, Plant %in% input$plant)
if(nrow(result) > 0){
latest_data(result)
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update the dataframe because the choice was not valid.\n")
}
})
observeEvent(input$type,{
if(! input$plant %in% my_data$Plant[my_data$Type == input$type]){
old <- input$plant
new <- my_data %>% filter(Type %in% input$type) %>% slice(1) %>% pull(Plant) %>% as.character()
updateSelectInput(session, "plant", selected = new)
cat(format(Sys.time(), "%H:%M:%S"), "Updated input$plant from", old, "to", new, "so that it represents a valid choice for", input$type, "\n")
}else{
cat(format(Sys.time(), "%H:%M:%S"), "Didn't update input$plant", input$plant, "because it is a valid choice for", input$type, "already\n")
}
})
observeEvent(input$plant,{
updateSelectInput(session, "type",
selected = my_data %>% filter(Plant %in% input$plant) %>% slice(1) %>% pull(Type))
})
output$plot <- renderDT({
cat(format(Sys.time(), "%H:%M:%S"), "updating datatable to only include", isolate(input$plant), "and", isolate(input$type), "\n\n")
latest_data()
datatable(latest_data())
})
}
)