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$group
is updated ->input$group_select
gets updated ->reactive
gets (re)executed with both updatedinput$group
andinput$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
observeEvent
described here (R shiny Observe running Before loading of UI and this causes Null parameters). However,observeEvent
ignores 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!
observeEvent
? Yes, I tried filtering the data when "group_select" widget appears withobserveEvent(input$group_select, {to_keep <- ...; data <- data[to_keep, ]})
. The problem is thatobserveEvent
seems to ignore the change when you unselect one or multiple categories in input$group_select. For instance, when "gender" is selected from groupselectInput
, "group_select" shows with both "male" and "female" checked initially. However if the users uncheck the male, data doesn't remove all the male rows. – statechulareventReactive
, it seems to meeventReactive
andobserveEvent
handles event-like expression in a similar way excepteventReactive
can return a value. Could you shed a bit more light on how to use it? – statechular