Today: Read, Modify, And Test A Simple App

By the end of this session, you should be able to:

  • Explain what a dashboard helps its reader do, and distinguish its UI from its server.
  • Trace how an input value reaches an output using matching names and $.
  • Modify and test a working example so a selector and slider update a histogram.
  • Explain the data and controls with brief text inside the app.

What we will build: one histogram app with a group selector, a bin-width slider, and useful explanations.

Dashboards Bring Useful Views Of Data Together

A dashboard brings selected plots, indicators, tables, and explanations together around a question or task.

It can help people:

  • Monitor: what is changing, and where might attention be needed?
  • Compare: how do groups, places, or periods differ?
  • Explore: does a pattern hold for a different selection of the data?

Interactive controls let the reader choose a view. A dashboard need not be live or real-time, and more controls do not automatically make it more useful.

Explore A Dashboard And Tell Us What It Does

We Can Build These Interactions In R

Change the group and bin width; hover over the bars.

A Shiny Dashboard Has An Interface And A Server

User interface (front end): the controls, plots, tables, layout, and explanations the user sees in the browser.

Server (back end): R code that selects data, calculates summaries, and generates the content for those outputs.

Reactivity connects them: Shiny tracks which outputs depend on which inputs and updates them when needed.

UI
Choose an output group
Server
Filter data / build the plot
UI
Display the histogram

The plot’s location belongs to the UI; the code that creates its content belongs to the server.

From Running Commands To Defining Dependencies

R is still the same language. Shiny adds a reactive programming framework.

Ordinary R script Reactive Shiny app
You run commands in sequence You define dependencies between inputs and outputs
Changing a variable does not automatically update earlier results Changing an input causes affected outputs to recalculate
You decide when to rerun the plotting code Shiny manages when the dependent plotting code runs

An input change does not restart the app or rerun its entire script.

Today: automatic updates inside renderPlot(). Week 7: use reactive() to share preparation across several outputs.

From One Plot To A Usable Dashboard

Over the Shiny weeks, you will learn to read, modify, and extend working app examples.

Week What you will learn What you will build
6 inputs, outputs, automatic updates, and help text one plot with a group selector and slider
7 shared reactive data, multiple outputs, and simple layout coordinated plots and a summary
8 useful controls, invalid selections, and Plotly reliable interaction, with hover and zoom
9 adapting an app to your data and testing its usability your assessment dashboard prototype
10 troubleshooting and reproducibility checks a tested app and a plan for remaining work

Clear explanations, meaningful comparisons, and small, testable changes matter throughout. Advanced generated UI is optional.

Download The Week 6 App And Worksheet

Download the Week 6 ZIP from NOW. Unzip the whole folder into your existing module project.

Your existing module project/
  week-06-shiny-basics-reactivity/
    minimal_app.R
    starter_app.R
    slider_app.R
    worked_example_app.R
    01_read_and_modify_a_shiny_app.Rmd
    data/
      participant_reaction_times.csv

Keep the files inside the Week 6 folder. Do not move individual scripts or CSVs elsewhere, or work inside the ZIP.

Open the Rmd for instructions. Run app scripts with Run App; we are not knitting the app inside R Markdown.

Build One App In Two Stages

First block Second block
Run and inspect a minimal app Predict the effect of bin width
Identify UI, server, and output names Add a slider using an example
Add a selector and connect it to a plot Explain both controls and test the app

Core outcome: one working histogram app with two purposeful controls and enough text for another person to understand it.

Each stage: Together: inspect an example, Your turn: modify and test, then Together: check what happened. No typing along with live coding.

Write A Function, Then Call It

Together: trace the argument, calculation, and result.

double <- function(number) {
  number * 2
}

double(5)
#> [1] 10
double(7)
#> [1] 14
  • <- stores the function under the name double.
  • number is an argument: each call supplies its value.
  • The body inside {} runs when called; its last expression supplies the result.

Your turn: complete triple() in the Rmd and test it with 5 and 7.

Together: check why defining a function is different from calling it.

A Function Can Return A Whole Data Frame

Together: follow the value from the function call into output.

make_table <- function(number) {
  data.frame(number = number, doubled = number * 2)
}

output <- make_table(1:5)
output
  number doubled
1      1       2
2      2       4
3      3       6
4      4       8
5      5      10

1:5 supplies five numbers. The function returns a two-column data frame, which we store as output. Here, output is just a name we chose.

Use $ To Select A Named Part

output$doubled
[1]  2  4  6  8 10

For a data frame, $doubled selects the column named doubled.

Your turn: in the Rmd, predict and test how to select the original number column.

Together: connect this to Shiny

  • input$number reads the current value of the control named number.
  • output$double <- ... connects a rendered result to the output named double.
  • Shiny supplies input and output as arguments to server. They are special named objects, not data frames; output is not the server’s return value.

A Complete App In A Few Lines

library(shiny)

ui <- fluidPage(
  numericInput("number", "Choose a number", value = 5),
  textOutput("double")
)

server <- function(input, output) {
  output$double <- renderText({
    input$number * 2
  })
}

shinyApp(ui, server)

Together: run minimal_app.R and locate the UI and server. The next two slides trace their connections.

The UI Names An Input And An Output

ui <- fluidPage(
  numericInput(
    inputId = "number",          # Internal input name
    label = "Choose a number",  # Visible label
    value = 5
  ),
  textOutput("double")          # Internal output name
)
Part Name in the UI Use in the server
Input control "number" input$number
Output location "double" output$double

UI elements are separated by commas. The final element has no trailing comma.

The Server Connects The Input To The Output

server <- function(input, output) {
  output$double <- renderText({  # Output name: double
    input$number * 2            # Input name: number
  })
}

output$double matches textOutput("double") in the UI.

renderText() reads input$number: change the input and Shiny recalculates the result.

Shiny calls server, supplying input and output. Control changes update dependent outputs, not the whole server function.

Your turn: predict the result for 7, then test it. Together: check which name connects to the control and which connects to the result.

Start With A Plot You Already Know

The supplied data contain 72 participant means, not trial-level reaction times. Each mean combines familiarisation conditions.

ggplot(d_participants,
       aes(x = mean_rt)) +
  geom_histogram(binwidth = 100) +
  labs(x = "Reaction time (ms)",
       y = "Participants")

The Shiny task is to let a reader change which observations appear and how they are grouped.

The Same Connection Pattern Works For Plots

Output location in the UI Content generated in the server
textOutput("double") output$double <- renderText(...)
plotOutput("rt_plot") output$rt_plot <- renderPlot(...)

The output type changes, but the names still have to match. The familiar ggplot code goes inside renderPlot().

Controls Need An Internal Name And A Label

selectInput(
  inputId = "modality",    # Internal input name
  label = "Output group", # Visible label
  choices = c("Spoken output" = "speech",
              "Written output" = "writing")
)
  • inputId = "modality" gives the control its internal name: read its value as input$modality.
  • The user sees the label and readable choice names.
  • Data values are speech and writing, not the displayed choice names.

Adding the selector alone does not change a plot that never uses its value.

A Filter Connects The Selector To The Plot

Together: filter() keeps rows that satisfy a condition, keeping all columns.

output$rt_plot <- renderPlot({
  selected_data <- filter(
    d_participants, modality == input$modality
  )
  ggplot(selected_data, aes(x = mean_rt)) +
    geom_histogram(binwidth = 100)
})

modality is a data column; input$modality is the selected value. == tests whether they are equal for each row.

Selecting "speech" keeps the 33 spoken-group rows. <- stores this subset as selected_data; the original data are unchanged.

Changing the selection makes renderPlot() recalculate. No reactive() function is needed yet.

Exercise 1: Add A Group Selector

Open starter_app.R; use Part 1 of the Rmd instructions (about 15 minutes).

  • Complete the UI output name, the filter’s input name, and the plotting dataset.
  • After editing code: save, stop the app, and run again.
  • Change the group: does the histogram update?

When testing controls: keep the app running.

The structure and selector are supplied. Use the completed reference whenever useful.

Your turn: make and test the three connections using the example.

Together: check which line makes the group selection affect the plot, and why the output names must match.

Mismatched Names Can Disconnect An Output

UI Server Result
plotOutput("rt_plot") output$rt_plot matching plot connection
plotOutput("reaction_plot") output$rt_plot no matching plot location

Together: predict what happens when only the UI name changes. Check how matching the two names repairs the connection.

Also check the Console for errors, commas between UI elements, and closing brackets. Test one change at a time.

Discussion: Does The Pattern Depend On Bin Width?

Both plots use the same 33 participants in the spoken-output group.

Which features persist? Which apparent peaks depend on the width? Why might a reader want to change it?

A Slider Can Control A Plot Parameter

Together: locate the slider’s name and use it in place of a fixed width.

Add the control to the UI:

sliderInput("binwidth",         # Internal input name
            "Bin width (ms)",  # Visible label
            min = 25, max = 300, value = 100, step = 25)

Use its name inside renderPlot():

geom_histogram(binwidth = input$binwidth)

Here, the selected value is a number. It replaces a fixed number in familiar ggplot code.

Different Controls Answer Different Questions

Control What changes? What stays fixed?
Output group which participants are included the meaning of reaction time
Bin width how reaction times are grouped into bins the selected participants and their values

input$modality and input$binwidth are both read inside the plot’s render function. A change to either causes the plot to update.

Do not infer a causal effect from differences between the two participant groups.

Explanatory Text Belongs In The App

# Describe what each observation represents.
p("Each value represents one participant's mean
   reaction time across familiarisation conditions.")

helpText("Only participants in the selected output
          group are included in the histogram.")

A useful explanation identifies the data, units, selection, and limits of interpretation.

A comment beginning with # is for the programmer. The app user cannot see it.

Exercise 2: Add A Slider And Explain Its Effect

Open slider_app.R; use Part 2 of the Rmd instructions (about 25 minutes).

  • Complete the slider label, starting value, input connection, and help text.
  • After editing code: save, stop the app, and run again.
  • Hold the group fixed while trying several widths.
  • Compare your help text with a neighbour’s: what changes with bin width, and what stays the same? Revise one sentence if needed.

When testing controls: keep the app running.

Keep text accurate and brief. Explain what each control changes, not just what it is called.

Your turn: complete and test the app; use the slider example as a model.

Together: check why changing bin width does not change the participant count. Compare the help text students wrote.

Next Week: Several Views Of The Same Selection

Today: controls update one plot through renderPlot().

Next week: add another plot and a summary, with shared preparation using reactive().

Later: layout, sensible input handling, Plotly, and adapting an app to your own data. Advanced generated UI is optional, not a requirement for a useful dashboard.

Start Exploring Possible Summative Datasets

Start looking for data on a topic that interests you for the summative assessment.

  • Browse the available variables and what each observation represents.
  • Keep the source link for anything that looks promising.
  • We will discuss dataset suitability and app ideas in later weeks.

It is fine not to have a dataset yet. No dataset choice or dashboard plan is required at this stage.

After Class: Read About Your First Shiny App

Everyone: read Wickham (2021), Chapter 1: Your first Shiny app, sections 1.1-1.5, ending with Adding behaviour.

As you read, connect the example to today’s app:

  • Which parts belong to the UI and which to the server?
  • Where do the input and output names connect?
  • Why does changing an input update an output?

No written submission or book exercises are required. Section 1.6 introduces shared reactive expressions: optional preparation for next week.

Optional reference: Shiny helpText documentation.

References

Wickham, H. (2021). Mastering shiny. O’Reilly Media.