Open your completed Week 7 layout_app.R and choose Run App.
Trace each input: which outputs change? Why did we use reactive() for the shared filter instead of repeating it inside each output?
Open your completed Week 7 layout_app.R and choose Run App.
Trace each input: which outputs change? Why did we use reactive() for the shared filter instead of repeating it inside each output?
Unzip psyc40940-week-08-tabs-controls.zip into your existing module project. Keep scripts beside data/ inside week-08-tabs-controls/.
Open session-guide.html in a web browser. Read it beside your code; edit the named app scripts in RStudio and use Run App to test them.
| Guide section | What you can add to an app |
|---|---|
| Exercise 1: build two tabs | Separate views into tabs with shared controls |
| Exercise 2A: filter groups and a range | Combine group and range filters across views |
| Exercise 2B: explain an empty selection | Explain empty results and help users recover |
Complete the blanks for the current checkpoint before running. The guide explains which code remains commented until later.
Wickham, §6.3: divide a complex page into manageable pieces.
Trade-off: the other plot is hidden. Show plots together when direct comparison matters.
The connections are unchanged. Keep controls and the count outside the tabs, and preserve the output names.
Predict: if the reader changes group while viewing one tab, the plot in the other tab will changed too.
mainPanel(
tabsetPanel(
tabPanel("Distribution",
p("Explain this view."),
plotOutput("rt_plot")
),
tabPanel("Individual values",
p("Explain this view."),
plotOutput("values_plot")
)
)
)One outer wrapper: tabsetPanel() holds all tabs.
One wrapper per view: tabPanel() gives a tab its visible name and contents.
The selector and slider remain in the existing sidebarPanel().
Keep "rt_plot" and "values_plot" matched to their existing server outputs. The server and plotting code stay the same for this exercise.
Follow Exercise 1: build two tabs in the guide and open starter_app.R (about 20 minutes).
The server is already complete. You are changing navigation and explanation around familiar plots.
| Test | Expected result |
|---|---|
| Select writing, then open each tab | Both views show the same 39 participants |
| Change bin width | Only the histogram changes |
| Switch tabs | Controls keep their values; count stays visible |
Explain: why could the server stay the same? When would showing both plots together be preferable?
Quick check (about 5 minutes): predict which rows remain with one group, both groups, or no groups selected.
checkboxGroupInput(
"groups", "Output groups",
choices = c("Spoken output" = "speech",
"Written output" = "writing"),
selected = c("speech", "writing")
)
choices lists the available values; selected ticks both initially. input$groups holds the current selection, which may contain one, two, or no values.
Use modality %in% input$groups to keep rows matching any selected group. == compares values element by element and was suitable for the earlier single choice.
Here "groups" names the control; modality remains the data column.
Break: we will use these group choices with a range filter after the break.
sliderInput("vocab_range", "Keep vocabulary scores within",
min = 0.80, max = 1.00,
value = c(0.80, 1.00), step = 0.01)
Why filter? We might want to examine reaction times among participants with vocabulary scores of 0.90–0.95, rather than everyone. The range selects which participants appear; the axes still show reaction time.
The handles supply a vector: [1] is its lower value; [2] is its upper value.
selected <- filter( d_participants, modality %in% input$groups, vocabulary_score >= input$vocab_range[1], vocabulary_score <= input$vocab_range[2] )
Each condition must hold. Vocabulary range selects participants; bin width groups their reaction times into bins.
Group and vocabulary range select participants; vocabulary score is not mapped to either plot axis. The count and both plots use the same result from selected_data().
Trace it: which input changes the histogram without changing the count or boxplot?
Open controls_app.R and follow Exercise 2A: filter groups and a range (about 12 minutes).
Complete four active blanks: checkbox function, group input name, and two range indexes. Leave the commented validation block for the next checkpoint.
Then clear both groups or use the range 0.80–0.81. Observe the result and restore a valid selection. We will now improve the feedback.
A reader’s subgroup of interest may contain no participants. What happened, and what could they change?
These checks run inside a reactive expression or a render block when its calculation is needed.
| Function | What happens when the check fails? |
|---|---|
req(input$vocab_range) |
Stop the current attempt silently if the range is unavailable |
need(condition, "message") |
Produce the failure message; on its own, it does not stop the calculation |
validate(need(...)) |
Stop and show that message in the affected outputs |
req(): an unavailable input can reach the filter and cause an R error. Our fixed slider normally supplies a default, so this is mainly a startup guard.Shiny documentation: req() and validate() / need().
selected_data <- reactive({
req(input$vocab_range)
selected <- filter(
d_participants,
modality %in% input$groups,
vocabulary_score >= input$vocab_range[1],
vocabulary_score <= input$vocab_range[2]
)
validate(need(
nrow(selected) > 0,
"Choose a group or widen the range."
))
selected
})1. Check the prerequisite: req() ends this attempt silently if the range is unavailable. Shiny tries again when the input changes; there is no waiting loop.
2. Check the result: need() tests whether rows remain and supplies a failure message. validate() acts on that result and stops the calculation if needed.
3. Return valid data: if the check passes, the last line returns selected. On failure, dependent outputs receive the validation feedback.
For an empty selection, validate(need(...)) explains what the reader can change. Using need() alone would let the calculation continue.
The checks are code inside one reactive expression, rather than additional reactive nodes.
req() stops this attempt silently.Continue in the same controls_app.R and follow Exercise 2B: explain an empty selection (about 13 minutes).
validate(need( to )).Tell the reader what happened and what to change. Check the count and each tab: they all depend on the shared preparation.
The guide explains how to uncomment the lines and lists the expected results.
Use your completed controls_app.R to fill these boxes and add arrows (3 minutes). Copy the boxes onto paper or use a drawing tool.
req() and validate(need()) inside the shared expression.Predict: what updates when the range changes? Does bin width rerun the filter? Tabs are not extra nodes.
Use your graph to explain the empty-selection and recovery tests you completed in Exercise 2B.
Review today’s choices: how would they help a reader answer a clear question?
The assessment permits at most three tabs and requires at least two interactive plots. Two purposeful tabs can be sufficient. Shiny-reactive plots qualify; Week 9 adds Plotly hover and zoom.
Read Wickham (2021):
Connect the examples to your app.
Bring a dataset for Week 9: have the data available in your project, the code to load it, its source, and one question you would like to explore. A candidate dataset is fine if you are still deciding; supplied practice data remain available as a backup.
If your core app works, use the remaining time for the density comparison. It extends Week 7’s multiple-group selection with a choice of display:
optional_density_app.R to compare the same selected groups using two density views.facet_wrap() line. Which view helps you describe the overall distribution, and which helps compare groups? Update the app explanation to match.tableOutput() and renderTable() example in extension_app.R. Check the count and median for each selected group.The tasks are independent. Summary and Density are alternative third tabs: choose one, or replace it when trying the other.
How do speech and writing reaction-time distributions differ? Open optional_density_app.R and follow Optional: compare groups with colour or panels in the guide. Complete its seven blanks.
radioButtons(
"density_view", "Compare groups using",
choices = c("Colour" = "colour",
"Separate panels" = "facets"),
selected = "colour"
)
Explain your preferred view in the app text. The guide explains the supplied if / else. Density shows shape, not participant counts.
| Control | Value supplied | Useful when |
|---|---|---|
selectInput() |
One selected value by default | A compact list of choices |
radioButtons() |
One selected value | A few alternatives should all stay visible |
checkboxGroupInput() |
Several selected values | Readers may include several groups |
numericInput() |
One number | Readers need to type an exact value |
checkboxInput() |
TRUE or FALSE |
A single option is on or off |
Choose a control for the value your calculation needs. The same input can guide filtering or a plotting choice, as our bin-width slider already does.
These are reference options for later project work; no additional functions are required today.
Now use worked_example_app.R to compare with your own version.
Compare both tabs, the shared controls, and all empty-selection tests. Explain how the shared preparation supplies valid data or feedback to each output.
Keep your own work. Use the reference to check your reasoning and app explanations.
Wickham, H. (2021). Mastering shiny. O’Reilly Media.