Lecturer: Michael Lydeamore
Department of Econometrics and Business Statistics
Important
You should be able to:
For a selected WPL season and run threshold, which leading batters remain, and how did they score?
The app will show:
All four claims should describe the same selection.
library(shiny)
library(bslib)
library(dplyr)
library(ggplot2)
batters <- read.csv("data/wpl_batters.csv")
ui <- page_sidebar(
title = "WPL batting explorer",
sidebar = sidebar(
selectInput(
"season", "Choose a season",
choices = sort(unique(batters$season))
),
sliderInput(
"minimum_runs", "Minimum runs",
min = 0,
max = max(batters$runs),
value = 150,
step = 25
)
),
layout_columns(
value_box(
title = "Batters shown",
value = textOutput("number_shown")
),
value_box(
title = "Leading run-scorer",
value = textOutput("leading_batter")
)
),
plotOutput("batters_plot"),
tableOutput("batters_table")
)
server <- function(input, output, session) {
output$number_shown <- renderText({
batters |>
filter(
season == input$season,
runs >= input$minimum_runs
) |>
nrow()
})
output$leading_batter <- renderText({
filtered <- batters |>
filter(
season == input$season,
runs >= input$minimum_runs
)
req(nrow(filtered) > 0)
filtered$striker[which.max(filtered$runs)]
})
output$batters_plot <- renderPlot({
batters |>
filter(
season == input$season,
runs >= input$minimum_runs
) |>
ggplot(aes(
x = dot_percent,
y = boundary_percent,
size = balls_faced
)) +
geom_point(colour = "#006DAE", alpha = 0.7) +
labs(
x = "Dot-ball percentage",
y = "Boundary percentage",
size = "Balls faced"
) +
theme_minimal(base_size = 14)
})
output$batters_table <- renderTable({
batters |>
filter(
season == input$season,
runs > input$minimum_runs
) |>
arrange(desc(runs)) |>
select(striker, batting_team, runs, balls_faced) |>
head(5)
})
}
shinyApp(ui, server)At an exact threshold, the app disagrees with itself.
Start a fresh R session, then enable the reactive log before running the app:
Change each input once, then open the visualiser:
The full log may also contain browser and layout nodes. Filter or double-click a named output to isolate the part of the graph we are discussing.
selected_batters <- reactive({
batters |>
filter(
season == input$season,
runs >= input$minimum_runs
)
}, label = "selected_batters")reactive() records the calculation and its reactive dependencies.label = "selected_batters" supplies a readable node name for reactlog.selected_batters() retrieves its current value.Without (), you have the reactive object—not the data it currently returns.
output$number_shown <- renderText({
nrow(selected_batters())
})
output$leading_batter <- renderText({
selected <- selected_batters()
selected$striker[which.max(selected$runs)]
})
output$batters_table <- renderTable({
selected_batters() |>
arrange(desc(runs)) |>
select(striker, batting_team, runs)
})server() with this versionserver <- function(input, output, session) {
selected_batters <- reactive({
batters |>
filter(
season == input$season,
runs >= input$minimum_runs
)
}, label = "selected_batters")
output$number_shown <- renderText({
nrow(selected_batters())
})
output$leading_batter <- renderText({
selected <- selected_batters()
req(nrow(selected) > 0)
selected$striker[which.max(selected$runs)]
})
output$batters_plot <- renderPlot({
selected_batters() |>
ggplot(aes(
x = dot_percent,
y = boundary_percent,
size = balls_faced
)) +
geom_point(colour = "#006DAE", alpha = 0.7) +
labs(
x = "Dot-ball percentage",
y = "Boundary percentage",
size = "Balls faced"
) +
theme_minimal(base_size = 14)
})
output$batters_table <- renderTable({
selected_batters() |>
arrange(desc(runs)) |>
select(striker, batting_team, runs, balls_faced) |>
head(5)
})
}input$season -------+
+---> selected_batters() --+---> number
input$minimum_runs -+ +---> leader
+---> plot
+---> tableShiny records a dependency whenever reactive code reads an input or another reactive value.
reactlog after refactoringStop the app, clear the old history and run the revised version:
Change one input and reopen reactlog.
The graph should now contain one labelled selected_batters node between the two inputs and all four outputs.
When input$minimum_runs changes, step through the reactive history:
selected_batters becomes invalid;The user changes input$minimum_runs.
selected_batters() is invalidated.Use a shared reactive when several consumers need the same value.
# Loaded once when the R process starts
batters <- read.csv("data/wpl_batters.csv")
server <- function(input, output, session) {
# Different for each user session
selected_batters <- reactive({
batters |>
filter(season == input$season)
})
}server() runs once for the R process and is shared across sessions.server() is created separately for each user session.Which object should own the filter?
3. It represents the current selection without duplicating the rule or mixing sessions.
Who is the audience?
What software do they have?
Who will run R?
Who controls access?
Who maintains the dependencies and data connections?
| R package | Hosted web app | |
|---|---|---|
| Primary audience | R users | Browser users |
| R process runs on | User’s computer | Hosting server |
| User receives | Installable software | A URL |
| User can reuse functions | Yes | Only through the interface |
| Author maintains | Package releases | A running service |
inst/ ships non-R files with the packageDuring installation, files under inst/ are copied into the installed package.
run_app <- function() {
app_dir <- system.file(
"app",
package = "wplcommunicate"
)
shiny::runApp(app_dir)
}system.file() finds the app after installation, wherever the package lives.
Local package
browser ↔︎ R on the user’s computer
Hosted app
user’s browser ↔︎ network ↔︎ R on a server
For a typical hosted deployment, the service needs:
Only files included in the deployment bundle are available to the server.
Imagine the hosting server has never seen your project.
Can it answer each question?
or use qualified calls:
| Route | Useful when |
|---|---|
shinyapps.io |
You want managed Shiny hosting with a straightforward rsconnect workflow |
| Posit Connect Cloud | You want GitHub-connected cloud publishing for public data products |
| Posit Connect | An organisation needs authentication, access control and managed publishing |
| Shiny Server | An organisation will operate its own open-source Linux server |
Summary
ETC5523 Week 11