I am building an explorative visual app based on the awesome R Shiny package. One thing the app will do is to read a real-valued "measurement" column and display a boxplot of those measurement values. In addition, there is an optional selectInput widget that allows the user to select a group variable to dive into. The group variable basically contains the names of columns having categorical values, e.g. gender (Female vs Male), country (US, CA, and etc). So if "gender" is selected from the group selectInput, the app will display two boxplots, one for the female and the other for the male.
To give the user the flexibility of focusing on certain categories, say only the female, I came up with the checkboxGroupInput that is only shown when group variable is selected with both choices and selected being initially set to all the categories of the group variable. So for instance, when the user only wants to see the boxplot of the female, he/she can easily un-check the check box of the male and the boxplot of the male get removed while that of the female remains.
Following warmoverflow's suggestion, here is a reproducible toy example
# global setup
library(shiny)
library(ggplot2)
library(dplyr)
set.seed(12345)
dummy_data <- data.frame(
Value = c(rnorm(50), 2 + rnorm(50)),
Gender = c(rep('Female', 50), rep('Male', 50)),
Country = c(rep('US', 50), rep('CA', 50)),
stringsAsFactors = FALSE
)
# ui function
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput('group', 'Group',
c('Choose'='', setdiff(names(dummy_data), 'Value'))),
uiOutput('group_select')
),
mainPanel(plotOutput('box_plot'))
)
)
# server function
server <- function(input, output) {
# the group level select/unselect widget
output$group_select <- renderUI({
req(input$group)
data <- dummy_data
group_levels <- unique(data[[input$group]])
conditionalPanel(
condition = "input.group != ''",
checkboxGroupInput('group_select', input$group,
choices = group_levels, selected = group_levels)
)
})
# filter the data if group variable is selected
data_to_plot <- reactive({
data <- dummy_data
if(!(is.null(input$group) || input$group == '')) {
data <- data %>%
mutate_(group_ = input$group) %>%
filter(group_ %in% input$group_select)
} else {
data$group_ <- as.factor(rep.int(1, nrow(data)))
}
return(data)
})
# show the boxplot
output$box_plot <- renderPlot({
data <- data_to_plot()
validate(
need(!(is.null(data) || nrow(data) == 0),
'There are no data to plot!')
)
ggplot(data = data, aes(factor(group_), Value)) + geom_boxplot()
})
}
shinyApp(ui = ui, server = server)
Well, the code works but reactive statement runs twice unnecessarily, once when input$group gets updated and another when input$group_select gets updated as you can notice the error message "There are no data to plot!" when you select "Gender" from the group drop-down list. So my question is: is there a way to ensure the following execution order:
input$groupis updated ->input$group_selectgets updated ->reactivegets (re)executed with both updatedinput$groupandinput$group_select
I have spent almost an entire day searching for solution to little avail.
- It seemed that Roger Day has solved this in Using sendInputMessage and receiveMessage to make shiny input objects reactive. But his method involves creating a jstree, which is totally an unfamiliar area to me and it looks like he has removed the solution from his Github.
- Another possible lead to follow is using
observeEventdescribed here (R shiny Observe running Before loading of UI and this causes Null parameters). However,observeEventignores it when you unselect one or multiple categories ininput$group_select.
The difficulty of it as pointed out by Roger Day is that the input update functions are ordinarily not reactive and thus (based on my experiments and understanding) get executed later than reactive statements. Or if there is a way to make input update function reactive, priority value can be applied to alter the execution order in the desirable way.
Any input is greatly appreciated! Sorry for the long description!