flowchart LR
DS["input$dataset"] -.-> D
L["input$load"] ==>|"trigger"| D["dataInput()<br/>eventReactive"]
D --> V["vars()<br/>reactive"]
V --> XU["output$x_ui / y_ui<br/>renderUI"]
XU --> X["input$x / input$y"]
X -.-> N
A["input$analyze"] ==>|"trigger"| N["newdata()<br/>eventReactive"]
D -.-> N
N --> G["output$graph"]
N --> S["output$summary"]
style L fill:#d9534f,color:#fff
style A fill:#d9534f,color:#fff
style DS fill:#2b6c8e,color:#fff
style X fill:#2b6c8e,color:#fff
style D fill:#f0ad4e,color:#fff
style V fill:#f0ad4e,color:#fff
style N fill:#f0ad4e,color:#fff
style XU fill:#8fd36f,color:#fff
style G fill:#8fd36f,color:#fff
style S fill:#8fd36f,color:#fff
Your app: explore a dataset
Putting it all together, step by step
We build the app that will go online on Day 2. The user:
- chooses a dataset (or uploads their own) and loads it
- chooses the X and Y variables among the columns of that dataset
- clicks to update the plot and the summary
This is the app we are going to build. Try it: load a dataset, choose the variables and click Update analysis.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 800
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars",
"Upload data" = "upload") # NEW option
),
# NEW: shown only when "Upload data" is selected
conditionalPanel(
condition = "input.dataset == 'upload'",
fileInput("file", "Select a CSV file", accept = ".csv"),
radioButtons("sep", "Column separator",
choices = c(";" = ";", "," = ","))
),
actionButton("load", "Load dataset"),
hr(),
uiOutput("x_ui"),
uiOutput("y_ui"),
actionButton("analyze", "Update analysis")
),
mainPanel(
plotOutput("graph"),
verbatimTextOutput("summary")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars,
upload = { # NEW
req(input$file) # NEW: wait for a file
read.csv(input$file$datapath, sep = input$sep) # NEW
})
})
# 2. VARIABLE NAMES
vars <- reactive({
names(dataInput())
})
# 3. DYNAMIC UI
output$x_ui <- renderUI({
selectInput("x", "X variable:",
choices = vars(),
selected = vars()[1])
})
output$y_ui <- renderUI({
selectInput("y", "Y variable:",
choices = vars(),
selected = vars()[min(2, length(vars()))])
})
# 4. DATA FOR THE ANALYSIS
newdata <- eventReactive(input$analyze, {
df <- dataInput()
df[, c(input$x, input$y), drop = FALSE]
})
# 5. OUTPUTS
output$graph <- renderPlot({
df <- newdata()
x <- df[[1]]
y <- df[[2]]
if (is.factor(x) || is.character(x)) {
boxplot(y ~ x, xlab = names(df)[1], ylab = names(df)[2])
} else {
plot(x, y, xlab = names(df)[1], ylab = names(df)[2], pch = 19)
}
})
output$summary <- renderPrint({
summary(newdata())
})
}
We build it in four steps. Each step is a working app: run it, check it, then move on. The files of each step are in apps/ (05-explore-step1 … 05-explore-step4).
Step 1 · Load a dataset
A dataset is loaded with an eventReactive(). The first rows are printed to check that the data are correct. Choose a dataset and click the button:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 330
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Load dataset")
),
mainPanel(
verbatimTextOutput("preview")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET (only when "Load dataset" is clicked)
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
# check that the data arrived
output$preview <- renderPrint({
head(dataInput())
})
}
The code:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 330
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Load dataset")
),
mainPanel(
verbatimTextOutput("preview")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET (only when "Load dataset" is clicked)
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
# check that the data arrived
output$preview <- renderPrint({
head(dataInput())
})
}
Step 3 · The analysis, on demand
A second button, analyze, and a second eventReactive() that freezes the selected columns when it is clicked. The plot and the summary use only these frozen data.
Try it: load cars, choose the variables, click Update analysis. Now change the variables without clicking: the plot does not move until you click again.
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 720
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Load dataset"),
hr(),
uiOutput("x_ui"),
uiOutput("y_ui"),
actionButton("analyze", "Update analysis") # NEW: second button
),
mainPanel(
plotOutput("graph"), # NEW
verbatimTextOutput("summary") # NEW
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
# 2. VARIABLE NAMES
vars <- reactive({
names(dataInput())
})
# 3. DYNAMIC UI
output$x_ui <- renderUI({
selectInput("x", "X variable:",
choices = vars(),
selected = vars()[1])
})
output$y_ui <- renderUI({
selectInput("y", "Y variable:",
choices = vars(),
selected = vars()[min(2, length(vars()))])
})
# 4. DATA FOR THE ANALYSIS (NEW): frozen until "Update analysis" is clicked
newdata <- eventReactive(input$analyze, {
df <- dataInput()
df[, c(input$x, input$y), drop = FALSE]
})
# 5. OUTPUTS (NEW)
output$graph <- renderPlot({
df <- newdata()
x <- df[[1]]
y <- df[[2]]
if (is.factor(x) || is.character(x)) {
boxplot(y ~ x, xlab = names(df)[1], ylab = names(df)[2])
} else {
plot(x, y, xlab = names(df)[1], ylab = names(df)[2], pch = 19)
}
})
output$summary <- renderPrint({
summary(newdata())
})
}
How does it work? In the server:
1newdata <- eventReactive(input$analyze, {
df <- dataInput()
2 df[, c(input$x, input$y), drop = FALSE]
})- 1
- Runs only when Update analysis is clicked
- 2
-
Keep only the two chosen columns.
input$xandinput$yare text (the column names), so we can use them to select columns
The plot adapts to the type of X: a boxplot if X is categorical, a scatterplot if it is numeric.
The complete code:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 720
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars")
),
actionButton("load", "Load dataset"),
hr(),
uiOutput("x_ui"),
uiOutput("y_ui"),
actionButton("analyze", "Update analysis") # NEW: second button
),
mainPanel(
plotOutput("graph"), # NEW
verbatimTextOutput("summary") # NEW
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars)
})
# 2. VARIABLE NAMES
vars <- reactive({
names(dataInput())
})
# 3. DYNAMIC UI
output$x_ui <- renderUI({
selectInput("x", "X variable:",
choices = vars(),
selected = vars()[1])
})
output$y_ui <- renderUI({
selectInput("y", "Y variable:",
choices = vars(),
selected = vars()[min(2, length(vars()))])
})
# 4. DATA FOR THE ANALYSIS (NEW): frozen until "Update analysis" is clicked
newdata <- eventReactive(input$analyze, {
df <- dataInput()
df[, c(input$x, input$y), drop = FALSE]
})
# 5. OUTPUTS (NEW)
output$graph <- renderPlot({
df <- newdata()
x <- df[[1]]
y <- df[[2]]
if (is.factor(x) || is.character(x)) {
boxplot(y ~ x, xlab = names(df)[1], ylab = names(df)[2])
} else {
plot(x, y, xlab = names(df)[1], ylab = names(df)[2], pch = 19)
}
})
output$summary <- renderPrint({
summary(newdata())
})
}
Step 4 · Upload your own data
Last step: a fourth option, Upload data, with value "upload": choices = c("rock", "pressure", "cars", "Upload data" = "upload"). When it is selected, a file input and the choice of the column separator appear.
Test files: example.csv (separator ,) and example-semicolon.csv (separator ;).
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [viewer]
#| viewerHeight: 800
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars",
"Upload data" = "upload") # NEW option
),
# NEW: shown only when "Upload data" is selected
conditionalPanel(
condition = "input.dataset == 'upload'",
fileInput("file", "Select a CSV file", accept = ".csv"),
radioButtons("sep", "Column separator",
choices = c(";" = ";", "," = ","))
),
actionButton("load", "Load dataset"),
hr(),
uiOutput("x_ui"),
uiOutput("y_ui"),
actionButton("analyze", "Update analysis")
),
mainPanel(
plotOutput("graph"),
verbatimTextOutput("summary")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars,
upload = { # NEW
req(input$file) # NEW: wait for a file
read.csv(input$file$datapath, sep = input$sep) # NEW
})
})
# 2. VARIABLE NAMES
vars <- reactive({
names(dataInput())
})
# 3. DYNAMIC UI
output$x_ui <- renderUI({
selectInput("x", "X variable:",
choices = vars(),
selected = vars()[1])
})
output$y_ui <- renderUI({
selectInput("y", "Y variable:",
choices = vars(),
selected = vars()[min(2, length(vars()))])
})
# 4. DATA FOR THE ANALYSIS
newdata <- eventReactive(input$analyze, {
df <- dataInput()
df[, c(input$x, input$y), drop = FALSE]
})
# 5. OUTPUTS
output$graph <- renderPlot({
df <- newdata()
x <- df[[1]]
y <- df[[2]]
if (is.factor(x) || is.character(x)) {
boxplot(y ~ x, xlab = names(df)[1], ylab = names(df)[2])
} else {
plot(x, y, xlab = names(df)[1], ylab = names(df)[2], pch = 19)
}
})
output$summary <- renderPrint({
summary(newdata())
})
}
How does it work? In ui.R, the file input and the separator are wrapped in a conditionalPanel():
1conditionalPanel(
2 condition = "input.dataset == 'upload'",
fileInput("file", "Select a CSV file", accept = ".csv"),
radioButtons("sep", "Column separator",
choices = c(";" = ";", "," = ","))
)- 1
- Shows its content only when the condition is true
- 2
-
The condition is written in JavaScript, not in R:
input.datasetwith a dot, and'upload'in quotes
In the server, a fourth case in the switch() of dataInput() must be added. The curly brackets hold more than one line of code:
- 1
-
req()= required: if no file has been uploaded yet, stop here silently, without an error - 2
- Read the file from the place where Shiny saved it, with the separator chosen by the user
The complete code:
#| '!! shinylive warning !!': |
#| shinylive does not work in self-contained HTML documents.
#| Please set `embed-resources: false` in your metadata.
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 800
## file: ui.R
library(shiny)
ui <- fluidPage(
titlePanel("Explore a dataset"),
sidebarLayout(
sidebarPanel(
selectInput(
"dataset",
"Choose a dataset:",
choices = c("rock", "pressure", "cars",
"Upload data" = "upload") # NEW option
),
# NEW: shown only when "Upload data" is selected
conditionalPanel(
condition = "input.dataset == 'upload'",
fileInput("file", "Select a CSV file", accept = ".csv"),
radioButtons("sep", "Column separator",
choices = c(";" = ";", "," = ","))
),
actionButton("load", "Load dataset"),
hr(),
uiOutput("x_ui"),
uiOutput("y_ui"),
actionButton("analyze", "Update analysis")
),
mainPanel(
plotOutput("graph"),
verbatimTextOutput("summary")
)
)
)
## file: server.R
library(shiny)
server <- function(input, output, session) {
# 1. LOAD THE DATASET
dataInput <- eventReactive(input$load, {
switch(input$dataset,
rock = rock,
pressure = pressure,
cars = cars,
upload = { # NEW
req(input$file) # NEW: wait for a file
read.csv(input$file$datapath, sep = input$sep) # NEW
})
})
# 2. VARIABLE NAMES
vars <- reactive({
names(dataInput())
})
# 3. DYNAMIC UI
output$x_ui <- renderUI({
selectInput("x", "X variable:",
choices = vars(),
selected = vars()[1])
})
output$y_ui <- renderUI({
selectInput("y", "Y variable:",
choices = vars(),
selected = vars()[min(2, length(vars()))])
})
# 4. DATA FOR THE ANALYSIS
newdata <- eventReactive(input$analyze, {
df <- dataInput()
df[, c(input$x, input$y), drop = FALSE]
})
# 5. OUTPUTS
output$graph <- renderPlot({
df <- newdata()
x <- df[[1]]
y <- df[[2]]
if (is.factor(x) || is.character(x)) {
boxplot(y ~ x, xlab = names(df)[1], ylab = names(df)[2])
} else {
plot(x, y, xlab = names(df)[1], ylab = names(df)[2], pch = 19)
}
})
output$summary <- renderPrint({
summary(newdata())
})
}
Put it in your project
Copy ui.R and server.R of apps/05-explore-step4 into your project my-shiny-app, replacing the old ones, and run it with Run App.
This is the app that goes online on Day 2.
Pick one or more:
- Add
iristo the datasets (remember to add the case indataInput()) - Use the wrong separator on purpose with
example.csv: what happens to the menus? Why? - Add a
tableOutput()that shows the first 6 rows ofnewdata() - Show the title of the plot with the names of the two variables (
main = paste(...))
selectInput():choices = c("rock", "pressure", "cars", "iris", "Upload data" = "upload");dataInput(): addiris = iristo theswitch()- With the wrong separator
read.csv()reads a single column with a long name: the menus offer only one variable ui.R:tableOutput("rows");server.R:output$rows <- renderTable({ head(newdata()) })plot(x, y, ..., main = paste(names(df)[2], "by", names(df)[1]))