EXECUTED WITH R 4.3.3
This script is run with R 4.3.3, by tools/data-science/run_r_equivalents.py, and the lab page shows what it printed and the plots it drew. Every number in its comments was checked against that output.
Straight from labs/course-6-r/18_shiny_app.R, unchanged.
# =====================================================================
# Run with R 4.3.3. _drive_18_shiny_app.py starts this app, opens it in
# Chromium, uploads a CSV and goes through its three tabs, for the
# screenshots on the lab page. (Until October 2026 R could not be installed
# where these labs are checked, so this file was desk-checked only.)
# =====================================================================
# Experiment 18: A Shiny app that lets users upload a CSV file
# No Python equivalent -- this demonstrates the Shiny framework itself.
#
# Run with: shiny::runApp("18_shiny_app.R")
# or paste into RStudio and click "Run App".
library(shiny); library(ggplot2); library(dplyr)
# Step 1: Lay out the page: the upload, the options and three tabs
ui <- fluidPage(
titlePanel("CSV Explorer"),
sidebarLayout(
sidebarPanel(
fileInput("file", "Upload a CSV file", accept = ".csv"),
checkboxInput("header", "File has a header row", TRUE),
uiOutput("column_picker"), # built dynamically from the file
sliderInput("bins", "Histogram bins:", min = 5, max = 50, value = 20),
hr(),
helpText("Upload any CSV. The app lists its columns and plots whichever",
"numeric column you choose.")
),
mainPanel(
tabsetPanel(
tabPanel("Data", tableOutput("preview")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Plot", plotOutput("histogram"))
)
)
)
)
server <- function(input, output, session) {
# ONE reactive, shared by every output. Written this way the file is read
# ONCE per upload; copying read.csv() into each render*() would read it
# three times.
# Step 2: Read the uploaded file once, in a reactive
data <- reactive({
req(input$file) # wait until a file is uploaded
read.csv(input$file$datapath, header = input$header,
stringsAsFactors = FALSE)
})
# Build the column dropdown from the uploaded file's numeric columns.
# Step 3: Build the column picker from the file
output$column_picker <- renderUI({
req(data())
nums <- names(data())[sapply(data(), is.numeric)]
selectInput("column", "Numeric column to plot:", choices = nums)
})
# Step 4: Fill the data, summary and plot tabs
output$preview <- renderTable({
head(data(), 10) # NOTE the parentheses: data()
})
output$summary <- renderPrint({
summary(data())
})
output$histogram <- renderPlot({
req(input$column)
ggplot(data(), aes(x = .data[[input$column]])) +
geom_histogram(bins = input$bins, fill = "#1e7fbf", colour = "white") +
labs(title = paste("Distribution of", input$column),
x = input$column) +
theme_minimal()
})
}
# Step 5: Run the app
shinyApp(ui = ui, server = server)
# THE THREE RULES THAT CAUSE MOST SHINY BUGS:
# 1. Call a reactive WITH parentheses: data(), never data.
# 2. input$x can only be read inside a reactive context -- reactive(),
# observe() or render*(). At the top level of server() it errors.
# 3. Every output ID must match: plotOutput("histogram") in the UI pairs
# with output$histogram in the server. A typo gives a blank panel and
# NO error message, so check spelling first when nothing appears.
#
# req() is the idiomatic way to wait for an input: it silently stops the
# reactive until its argument is available, instead of erroring on NULL.
This one is R-specific — there is no Python equivalent in this lab.
The theory behind it is in this course’s units; the whole lab, with all 18 experiments, is on the lab page.