Level Up with Shiny for R
posit::conf(2024)
2024-08-12
04:00
Play with the app in the Reactivity page.
(It’s also _examples/08-reactivity/01-simple
, but don’t look at the code.)
How are the inputs connected to the outputs?
Draw a picture mapping inputs to the outputs.
Do you think there are any hidden dependencies?
Reactive values
Reactive expressions
Observers
Invalidated
Evaluating
Ready
💡 While the app is running, press Cmd/Ctrl + F4
to add markers in the timeline.
Close the app and run shiny::reactlogShow()
in the console.
#| standalone: true
#| components: [viewer]
#| viewerHeight: "100%"
library(shiny)
library(bslib)
library(dplyr)
library(collegeScorecard)
reactlog::reactlog_enable()
# UI ----------------------------------------------------------------------
ui <- page_sidebar(
theme = bs_theme(version = 5),
title = "College Affordability Analyzer",
fill = FALSE,
sidebar = sidebar(
open = list(mobile = "always-above"),
selectInput("state", "State", choices = setNames(state.abb, state.name), selected = "WA"),
sliderInput("sat_score", "Minimum SAT Score", min = 800, max = 1200, value = 1000, step = 10),
radioButtons(
"income_bracket",
"Income Bracket",
inline = TRUE,
choices = c(
"< $30k" = "cost_avg_income_0_30k",
"$30 - $48k" = "cost_avg_income_30_48k",
"$48 - $75k" = "cost_avg_income_48_75k",
"$75 - $110k" = "cost_avg_income_75_110k",
"$110k+" = "cost_avg_income_110k_plus"
)
)
),
value_box(
"Average 10-year Median Earnings",
textOutput("txt_amnt_earnings"),
showcase = bsicons::bs_icon("wallet-fill")
),
value_box(
"Average 4-year Cost",
textOutput("txt_four_year_cost"),
"For the selected income bracket",
showcase = bsicons::bs_icon("cash-stack")
),
card(
reactlog::reactlog_module_ui()
)
)
# Server ------------------------------------------------------------------
server <- function(input, output, session) {
reactlog::reactlog_module_server()
filtered_data <- reactive({
scorecard |>
filter(
academic_year == "2020-21",
!is.na(cost_avg),
!is.na(amnt_earnings_med_10y)
) |>
select(id, cost_avg, amnt_earnings_med_10y, score_sat_avg, starts_with("cost_avg_income")) |>
left_join(school, by = join_by(id)) |>
filter(
state == input$state,
score_sat_avg >= input$sat_score
)
})
output$txt_amnt_earnings <- renderText({
filtered_data() |>
pull(amnt_earnings_med_10y) |>
mean(na.rm = TRUE) |>
scales::dollar(accuracy = 10)
})
output$txt_four_year_cost <- renderText({
avg_cost <-
filtered_data() |>
pull(!!input$income_bracket) |>
mean(na.rm = TRUE)
scales::dollar(avg_cost * 4, accuracy = 10)
})
}
shinyApp(ui, server)
#| standalone: true
#| components: [viewer]
#| viewerHeight: "100%"
library(shiny)
library(bslib)
library(dplyr)
library(collegeScorecard)
reactlog::reactlog_enable()
# UI ----------------------------------------------------------------------
ui <- page_sidebar(
theme = bs_theme(version = 5),
title = "College Affordability Analyzer",
sidebar = sidebar(
open = list(mobile = "always-above"),
selectInput("state", "State", choices = setNames(state.abb, state.name), selected = "WA"),
sliderInput("sat_score", "Minimum SAT Score", min = 800, max = 1200, value = 1000, step = 10),
input_switch("by_income_bracket", "By Income Bracket", value = TRUE),
radioButtons(
"income_bracket",
"Income Bracket",
inline = TRUE,
choices = c(
"< $30k" = "cost_avg_income_0_30k",
"$30 - $48k" = "cost_avg_income_30_48k",
"$48 - $75k" = "cost_avg_income_48_75k",
"$75 - $110k" = "cost_avg_income_75_110k",
"$110k+" = "cost_avg_income_110k_plus"
)
)
),
value_box(
"Average 10-year Median Earnings",
textOutput("txt_amnt_earnings"),
showcase = bsicons::bs_icon("wallet-fill"),
fill = FALSE
),
value_box(
"Average 4-year Cost",
textOutput("txt_four_year_cost"),
"For the selected income bracket",
showcase = bsicons::bs_icon("cash-stack"),
fill = FALSE
),
card(
reactlog::reactlog_module_ui()
)
)
# Server ------------------------------------------------------------------
server <- function(input, output, session) {
reactlog::reactlog_module_server(height = "100%")
filtered_data <- reactive({
scorecard |>
filter(
academic_year == "2020-21",
!is.na(cost_avg),
!is.na(amnt_earnings_med_10y)
) |>
select(id, amnt_earnings_med_10y, score_sat_avg, starts_with("cost_avg")) |>
left_join(school, by = join_by(id)) |>
filter(
state == input$state,
score_sat_avg >= input$sat_score
)
})
output$txt_amnt_earnings <- renderText({
filtered_data() |>
pull(amnt_earnings_med_10y) |>
mean(na.rm = TRUE) |>
scales::dollar(accuracy = 10)
})
output$txt_four_year_cost <- renderText({
cost_var <- if (input$by_income_bracket) {
input$income_bracket
} else {
"cost_avg"
}
avg_cost <-
filtered_data() |>
pull(!!cost_var) |>
mean(na.rm = TRUE)
scales::dollar(avg_cost * 4, accuracy = 10)
})
}
shinyApp(ui, server)
04:00
I'm working on an example Shiny for R app for a workshop. I'm using the US College Scorecard dataset from the `collegeScorecard` package which provides the data separated into "school" and "scorecard" tables with these columns:
school: id, name, city, state, zip, latitude, longitude, url, deg_predominant, deg_highest, control, locale_type, locale_size, adm_req_test, is_hbcu, is_pbi, is_annhi, is_tribal, is_aanapii, is_hsi, is_nanti, is_only_men, is_only_women, is_only_distance, religious_affiliation
scorecard: id, academic_year, n_undergrads, cost_tuition_in, cost_tuition_out, cost_books, cost_room_board_on, cost_room_board_off, cost_avg, cost_avg_income_0_30k, cost_avg_income_30_48k, cost_avg_income_48_75k, cost_avg_income_75_110k, cost_avg_income_110k_plus, amnt_earnings_med_10y, rate_completion, rate_admissions, score_sat_avg, score_act_p25, score_act_p75, score_sat_verbal_p25, score_sat_verbal_p75, score_sat_math_p25, score_sat_math_p75
I'd like to have a VERY SIMPLE app that has:
* Four inputs
* Two reactive expressions. The first uses only two inputs. The second uses the third input and the first reactive expression.
* The fourth input is used in an observer to update an input.
* There are two outputs, each powered by one of the reactive expressions.
This app...
#| standalone: true
#| components: [viewer]
#| viewerHeight: "100%"
library(shiny)
library(bslib)
library(dplyr)
library(collegeScorecard)
reactlog::reactlog_enable()
# UI ----------------------------------------------------------------------
ui <- page_sidebar(
theme = bs_theme(version = 5),
title = "College Affordability Analyzer",
sidebar = sidebar(
open = list(mobile = "always-above"),
selectInput("state", "State", choices = setNames(state.abb, state.name), selected = "WA"),
sliderInput("sat_score", "Minimum SAT Score", min = 800, max = 1200, value = 1000, step = 10),
input_switch("by_income_bracket", "By Income Bracket", value = TRUE),
radioButtons(
"income_bracket",
"Income Bracket",
inline = TRUE,
choices = c(
"< $30k" = "cost_avg_income_0_30k",
"$30 - $48k" = "cost_avg_income_30_48k",
"$48 - $75k" = "cost_avg_income_48_75k",
"$75 - $110k" = "cost_avg_income_75_110k",
"$110k+" = "cost_avg_income_110k_plus"
)
)
),
value_box(
"Average 10-year Median Earnings",
textOutput("txt_amnt_earnings"),
showcase = bsicons::bs_icon("wallet-fill"),
fill = FALSE
),
value_box(
"Average 4-year Cost",
textOutput("txt_four_year_cost"),
"For the selected income bracket",
showcase = bsicons::bs_icon("cash-stack"),
fill = FALSE
),
card(
reactlog::reactlog_module_ui()
)
)
# Server ------------------------------------------------------------------
server <- function(input, output, session) {
reactlog::reactlog_module_server(height = "100%")
filtered_data <- reactive({
scorecard |>
filter(
academic_year == "2020-21",
!is.na(cost_avg),
!is.na(amnt_earnings_med_10y)
) |>
select(id, amnt_earnings_med_10y, score_sat_avg, starts_with("cost_avg")) |>
left_join(school, by = join_by(id)) |>
filter(
state == input$state,
score_sat_avg >= input$sat_score
)
})
observeEvent(input$state, {
output$txt_amnt_earnings <- renderText({
filtered_data() |>
pull(amnt_earnings_med_10y) |>
mean(na.rm = TRUE) |>
scales::dollar(accuracy = 10)
})
})
output$txt_four_year_cost <- renderText({
cost_var <- if (input$by_income_bracket) {
input$income_bracket
} else {
"cost_avg"
}
avg_cost <-
filtered_data() |>
pull(!!cost_var) |>
mean(na.rm = TRUE)
scales::dollar(avg_cost * 4, accuracy = 10)
})
}
shinyApp(ui, server)