ETC5523: Communicating with Data
Workshop 9: Build a Shiny app from scratch
🎯 Objectives
By the end of this workshop, you will be able to:
- create a complete Shiny app in a new
app.Rfile; - connect input controls to reactive outputs;
- use a reactive expression to share filtered data; and
- improve an app with purposeful labels, guidance and source information.
You will build an app that lets a cricket follower choose a Women’s Premier League season and explore how leading batters score their runs.
Start with a blank app.R. Build and run each stage before moving to the next one.
1. Prepare the app and data (4 minutes)
- Create a new folder called
wpl-app. - Inside that folder, create a blank file called
app.R. - Download
wpl_batters.csvand save it in the same folder. - Add code to load these packages:
shinybslibdplyrggplot2
- Read the CSV into an object called
wpldata.
Run the first few lines and inspect the data with glimpse(wpldata).
Each row describes one batter in one season. The variables include runs, balls faced, dot-ball percentage and boundary percentage. The data contain the 20 leading run-scorers in each season who faced at least 50 balls.
2. Create the smallest possible app (4 minutes)
A Shiny app needs a UI, a server function and a call to shinyApp().
Write these three components in app.R:
ui <- page_fluid(
"My first Shiny app"
)
server <- function(input, output, session) {
}
shinyApp(ui, server)Run the app. You should see the text My first Shiny app in a browser or viewer window.
Run the app after every small change. When something breaks, the most recent change is the first place to look.
3. Give the user a choice (5 minutes)
Replace page_fluid() with page_sidebar() and add a sidebar containing a selectInput().
Your input should:
- have the ID
"season"; - use the label Choose a season; and
- take its choices from
wpldata$season.
Run the app and change the selected season. Nothing else should happen yet.
4. Reserve a place for the plot (3 minutes)
Add this UI function after the sidebar:
plotOutput("batters_plot")Answer these questions before continuing:
- Does
plotOutput()draw a plot? - What output ID have you created?
- What will that ID look like inside the server function?
5. Connect the input to the output (8 minutes)
Inside server(), assign renderPlot() to output$batters_plot.
The plotting code should:
- filter
wpldatatoseason == input$season; - map
dot_percentto the x-axis; - map
boundary_percentto the y-axis; and - draw the observations with
geom_point().
Run the app and confirm that the plot changes when you select another season.
Trace the reactive path:
selectInput("season", ...) → input$season → renderPlot() → plotOutput("batters_plot")
6. Add a second input (7 minutes)
Let the user choose how many leading run-scorers to display.
- Add a
sliderInput()with the ID"top_n". - Let the user choose from 5 to 20 batters, with a default of 10.
- After filtering by season, use
slice_max()to keepinput$top_nbatters ranked byruns. - Map
balls_facedto point size.
Before running the app, predict which expression will rerun when the slider moves.
7. Create and reuse reactive data (7 minutes)
Move the filtering and ranking into a reactive expression called selected_batters.
Then:
- use
selected_batters()insiderenderPlot(); and - create a dynamic plot title with
renderText()andtextOutput().
The title should include the selected season and number of batters.
8. Make the app communicate (7 minutes)
Improve the app for someone who did not write it.
Add:
- an informative app title;
- percentage labels on both axes;
- a sentence explaining how to read the chart;
- a note explaining which batters are included; and
- a linked source for the data.
Do not add another feature until a reader can understand the current output.
9. Test another student’s app (5 minutes)
Swap with a partner. Do not explain your app before they use it.
The tester should:
- select another season;
- change the number of batters;
- explain what the upper-left of the chart means; and
- identify the data scope and source.
The author should record one programming problem and one communication problem, then fix the more consequential one.
Extension: choose one purposeful addition
If your app is working, add one feature that supports a clear user question:
- colour points by
batting_team; - label the three highest run-scorers;
- add a table made with
renderTable()andtableOutput(); or - add hover details with Plotly.
Before coding, complete:
This feature helps the user answer …