Recall Task: Connect The UI And Server

UI: input controls and output locations

The controls let users supply values. plotOutput() creates a named placeholder for the server to fill; Wickham (2021), section 2.3.

selectInput("modality", "Output group",
  choices = c("Spoken output" = "speech",
              "Written output" = "writing"))
sliderInput("binwidth", "Bin width (ms)",
  min = 25, max = 300, value = 100)
plotOutput("rt_plot")

Server: read inputs and calculate outputs

input$ reads control values; output$ defines displayed results using render functions. Dependent outputs update when inputs change.

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

Your task: match the two controls and plot location to their server counterparts. Identify the matching names. Predict each control’s effect.

Distinguish The Data Column From The Input

The data column in d_participants and control input$modality share a name. filter() explicitly connects the column to the selected value.

filter(d_participants, modality == input$modality)

Inside filter(), modality is the data column; input$modality is the chosen value. If it is "speech", the comparison keeps the 33 spoken-output participants.

ppt_id modality mean_rt
40 speech 1358.1
41 speech 1273.9
1 writing 2146.4
2 writing 1485.8

ppt_id: participant code.

modality: categorical data column containing "speech" or "writing".

mean_rt: numeric participant mean reaction time.

Recall: Trace The Histogram Dependencies

A reactivity graph shows dependencies: follow arrows to predict what an input change affects.

The Week 6 group input feeds the filter and histogram; bin width feeds the histogram. Both filtering and plotting are inside one renderPlot block.

  • Group changes: select different rows and redraw the histogram.
  • Bin width changes: redraw the histogram. The same rows are selected, but its filter runs again.

Wickham (2021), section 3.3.3.

Today: Keep Several Views Consistent

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

  • Share one filtering step across several outputs using reactive().
  • Obtain the current result from a reactive expression you created.
  • Explain which controls affect which outputs.
  • Organise the same app using a sidebar and clear explanations.

What we will build: a complementary plot of individual values, and a participant count, all using one group selection.

Download The Apps And Session Guide

Unzip the whole Week 7 folder from NOW into your existing module project.

Your existing module project/
  week-07-reactivity-layouts/
    intro_app.R
    comparison_app.R
    starter_app.R
    layout_app.R
    session-guide.html
    data/
      participant_reaction_times.csv

Keep the scripts and data/ together.

Open session-guide.html in a web browser; keep it beside your app script in RStudio.

Open The Session Guide In Your Browser

Read session-guide.html in your browser. Use its contents menu to find each stage; edit and run the named app scripts in RStudio.

Guide section Activity
Inspect the familiar histogram Recall the group selector and width slider
Add two views of the same selection Inspect extra outputs and repeated filtering
One shared filter supplies the outputs Trace the shared filter into the outputs
Exercise 1: connect shared data to the outputs Complete and test shared-data connections
Exercise 2: give the same app a sidebar Complete and test the sidebar layout

Where to complete code: read instructions in the session guide; complete the exercises in the named app .R script. Edit it, save, then click Run App.

Start With The Familiar Histogram

Run intro_app.R. This is the completed Week 6 app: one histogram, a group selector and a bin-width slider.

  • Select spoken output and set bin width to 100 ms.
  • Change group: which part of the data selection changes?
  • Keep the group fixed and change bin width: what changes about the histogram?

Point to input$modality and input$binwidth in the server. Both already make the plot update automatically.

Add Two Views Of The Same Selection

Stop intro_app.R and run comparison_app.R. It adds a boxplot/points and count for the same 33 spoken-output participants.

  • Histogram: reveals distribution shape through bin counts; bin width affects its appearance.
  • Boxplot: highlights the median and the spread of the middle 50% of values.
  • Points: reveal individual values, clusters, and extremes that a summary can conceal.

Three Outputs Repeat The Same Preparation

In comparison_app.R, the same filtering step appears inside each render block:

selected_data <- filter(d_participants, modality == input$modality)
Render block What it does with the selected rows
renderText() counts the participants
First renderPlot() draws a histogram
Second renderPlot() draws a boxplot with individual points

Predict and test: changing group updates the count and both plots; changing only bin width leaves the count and boxplot unchanged. How many times does each action run the filter?

The app works: each output filters separately. Changing bin width reruns the histogram’s whole render block, including its filter.

Compare The Graphs Before And After Sharing

Arrows show dependencies: follow them to predict changes.

Before: group selection feeds each render-block boundary; bin width feeds the histogram render-block boundary, rerunning its filter and plotting code. After: group selection feeds a shared reactive filter; bin width redraws only the histogram using cached rows.

  • Change group: three filters before; one shared filter after (all outputs visible).
  • Bin width: before, rerun the filter and plot; after, reuse cached rows.

Wickham (2021), section 3.3.3: The reactive graph.

One Shared Filter Supplies The Outputs

server <- function(input, output) {
  # 1. Prepare the selected rows once.
  selected_data <- reactive({
    filter(d_participants,
           modality == input$modality)
  })

  # 2. Draw the histogram from those rows.
  output$rt_plot <- renderPlot({
    ggplot(selected_data(),
           aes(x = mean_rt)) +
      geom_histogram(
        binwidth = input$binwidth)
  })

  # 3. Count the same rows.
  output$selection_note <- renderText({
    paste("Participants shown:",
          nrow(selected_data()))
  })
}

1. One shared preparation

reactive() creates a function, not a data frame. selected_data names that function; selected_data() calls it and returns the current data frame. Hence the parentheses; Wickham (2021), section 1.6.

2. A plot uses the result

The result is cached until group changes. The histogram also reads input$binwidth.

3. The count uses the same result

nrow() counts participants; paste() joins the label and count. Neither reads bin width.

Predict: if input$modality stays fixed and bin width changes, which outputs should update?

Share The Work And Change The Code Once

server <- function(input, output) {
  # 1. Prepare the selected rows once.
  selected_data <- reactive({
    filter(d_participants,
           modality == input$modality)
  })

  # 2. Draw the histogram from those rows.
  output$rt_plot <- renderPlot({
    ggplot(selected_data(),
           aes(x = mean_rt)) +
      geom_histogram(
        binwidth = input$binwidth)
  })

  # 3. Count the same rows.
  output$selection_note <- renderText({
    paste("Participants shown:",
          nrow(selected_data()))
  })
}

Change the rule in one place

Edit the filter inside reactive(), rather than in three output blocks. All outputs use the same rule.

Calculate once and share the result

When group changes, filtering runs once when needed. The histogram, boxplot, and count reuse the selected rows.

Keep results when other inputs change

Changing only bin width redraws the histogram using the cached rows. It does not rerun the filter.

Wickham (2021), section 1.6.

Keep this example open during Exercise 1.

Exercise 1: Connect The Shared Data

Open starter_app.R; follow Exercise 1 in the session guide (about 20–25 minutes).

Complete six blanks:

  • One function name to create the reactive expression.
  • Three calls obtaining the selected data: two plots and one count.
  • Two matching output names in the UI.

Complete all blanks, then predict and test each control change. Use the annotated shared-filter example when needed.

After code edits: save, stop, and run again. When testing controls: keep the app running.

Label The Reactivity Graph

This smaller app has a group selector (modality), a width slider (binwidth), a histogram and a participant count.

Inside the server function

selected_data <- reactive({
  filter(d_participants,
    modality == input$modality)
})
output$rt_plot <- renderPlot({
  ggplot(selected_data(), aes(mean_rt)) +
    geom_histogram(
      binwidth = input$binwidth)
})
output$selection_note <- renderText({
  nrow(selected_data())
})

Your task: label boxes A–E (3 minutes)

Five blank boxes A to E: A feeds C; C feeds D and E; B feeds E. Inputs are teal, the shared reactive is yellow and outputs are grey.

Predict: which output stays unchanged when only bin width changes?

Check The Names And Predict A Change

Inside the server function

selected_data <- reactive({
  filter(d_participants,
    modality == input$modality)
})
output$rt_plot <- renderPlot({
  ggplot(selected_data(), aes(mean_rt)) +
    geom_histogram(
      binwidth = input$binwidth)
})
output$selection_note <- renderText({
  nrow(selected_data())
})

Completed dependencies

Completed graph connecting input$modality to selected_data(), both outputs to selected_data(), and input$binwidth only to output$rt_plot.

Group: updates shared rows and both outputs. Bin width: redraws only the histogram, using cached rows.

Check participant counts in your app. Then take the break; we resume with layouts.

Which Layout Is Clearer?

Both versions contain the same data, controls, and outputs.

A: One long columnThe working introductory app with controls and plots in one long column
B: Controls beside the outputsThe same app arranged with controls in a sidebar and both plots in the main area

Where do you look for controls? Which arrangement helps you compare the plots and see what is selected?

Move UI Elements And Keep Their Connections

UI structure; existing elements omitted here

ui <- fluidPage(
  titlePanel("Participant reaction times"),
  p("Participant means, in milliseconds."),
  sidebarLayout(
    sidebarPanel(
      # Existing controls and help text.
    ),
    mainPanel(
      # Existing count, headings and plots.
    )
  )
)

Outer wrapper: sidebarLayout() holds both areas.

Controls: sidebarPanel() holds the selector, slider and their helpText("...").

Outputs: mainPanel() holds the count, headings and plots.

Keep input and output names unchanged when moving them. The server can stay the same.

Use p("...") to introduce the data and units; explain each control beside it. Write your own wording in the supplied blanks.

Predict: what will change when a plot moves to the main area? What will stay the same?

Exercise 2: Give The Same App A Sidebar

Open layout_app.R; follow Exercise 2 in the session guide (about 20–25 minutes).

Complete five blanks:

  • Three layout function names: the wrapper, control area, and output area.
  • One sentence identifying the data and units.
  • One sentence explaining which outputs each control changes.

The completed server is supplied.

Choose which plot should come first. Move its heading with its plot location; keep the output names and server unchanged. Predict the result, then test both controls.

Compare The Design And Test The Behaviour

Compare your layout and help text with a neighbour’s:

  • Which plot do you want a reader to inspect first, and why?
  • Does the help text explain what changes and what stays fixed?
  • Are units and the participant count easy to find?

Revise one sentence or one placement if needed.

Then repeat the control tests: group changes update all views; bin-width changes update only the histogram.

Together: check: which changes were only in the UI? Why could the server stay the same?

After Completing Your App: Compare And Explain

Now open worked_example_app.R to compare with your own finished app.

Trace the group selector through selected_data() to the count and both plots. Explain why bin width affects only the histogram.

Compare the layout and wording. Keep your own version and use the reference to check your reasoning.

What We Have Added This Week

  • One shared preparation step with reactive().
  • Two complementary plots of the same selected participants.
  • A count that identifies the selected sample.
  • Controls and explanations organised beside the outputs.

Automatic updates still work. No update or reset buttons are needed.

Next week: organise plots into tabs, choose controls, and explain empty selections. Session 9 introduces Plotly before adapting the ideas to another dataset.

After Class: Read About Shared Reactive Data

Everyone: Wickham (2021), section 1.6.

Where is the shared preparation in the example, and which outputs use it? No written response is required.

Optional reference: Chapter 6, section 6.2, for sidebar layouts.

Keep your working app. Continue exploring possible summative datasets; it is fine not to have chosen one yet. No transfer to your own data is required this week.

Independent Practice: Choose One Optional Exercise

Use the remaining time to work independently on one of two optional exercises. Choose the one you would most like to explore:

  • Compare both groups: use optional_groups_app.R to select one or both groups, compare them in the boxplot, and pool them in the histogram. Follow Optional: show both groups in the guide.
  • Add a summary table: complete five blanks in extension_app.R to connect and explain a participant count, median and interquartile range. Follow Optional: add a summary table in the guide.

The exercises are independent: neither requires completing the other. Read the instructions, make the changes in your app script, and test the controls.

If you finish one, you are welcome to try the other or continue after class.

Optional: Compare Both Groups In One View

Use Optional: show both groups in the guide and optional_groups_app.R. Complete its four blanks, save, then click Run App.

  • Use checkboxGroupInput() so users can tick one or both groups.
  • Filter with modality %in% input$modality: keep rows matching any selected group. == was for one selected value.
  • Map the boxplot’s x axis to modality to show separate groups and points.
  • Keep the histogram as aes(x = mean_rt): it pools both groups.
  • Write help text explaining the difference between the two views.

Check: counts should be 33, 39, and 72. Bin width changes only the histogram.

What might a pooled histogram conceal that the separate boxplots reveal? The guide explains the choices vector and %in%.

Optional: Add A Summary Table

Open extension_app.R and follow Optional: add a summary table in the session guide. The core app is complete; fill its five blanks:

  • Explain the summary in p("---").
  • Use tableOutput() for the named UI location.
  • Match the server output name and use renderTable().
  • Give summarise() the current result of your shared reactive expression.

The supplied calculations report participant count, median and IQR in milliseconds. Complete all blanks, save and use Run App.

Test: group changes update the table; bin width does not. Check the count against 33 / 39 and the summaries against the boxplot.

The guide explains each function and test. Tabs are a core part of Session 8.

References

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