Chapter 2 Inputs and outputs
In Chapter 2, we will go through how inputs and outputs work, and how to add them into your Shiny apps. Finally, we will build a “simple” app with reactivity.
2.1 Introduction to inputs and outputs
2.1.1 What are inputs?
Shiny inputs are widgets that let users enter data into the UI. They come in different types, depending on the kind of data you want users to provide, such as numbers, text or dates.
The values chosen by users can be used to filter or transform data in the app’s server. All inputs are added to the UI using specific input functions.
You can customise these input widgets by passing different arguments to the functions, allowing you to change options and messages that users see.
We have seen an example of an input with the sliderInput()
which creates a slider widget. In the example below, the user can choose a number between 1 and 100, with a default value of 50 (meaning it’s on 50 when users open the app).
2.1.2 What are outputs?
Shiny outputs are objects that let users visualise data in the UI. These can include text, charts, tables and other elements that display data from the server.
The data for these outputs can be reactive, meaning the outputs can change based on user input or other information from the app.
You add outputs to both the UI and server using render()
and output$
(don’t worry we will cover this later!).
Example in the UI:
shinyUI(
fluidPage(
titlePanel("Title"),
sidebarLayout(
sidebarPanel(
# Slider input to allow users to select a number
sliderInput("num",
"Choose a number:",
min = 1,
max = 100,
# Default value
value = 50)
),
mainPanel(
# Output for displaying the text
textOutput("selectedNum"),
# Output for displaying the plot
plotOutput("numberPlot")
)
)
)
)
Example in the server:
shinyServer(
function(input, output) {
# Display the selected number
output$selectedNum <- renderText({
# "Paste" the selected value
paste("You selected:", input$num)
})
# Create a plot based on the selected number
output$numberPlot <- renderPlot({
barplot(c(input$num, 100 - input$num),
names.arg = c("Selected", "Remaining"),
col = c("blue", "red"),
main = "Bar Plot of Selected Number")
})
})
- outputs: In this example, we have a text output that displays the selected number from the slider and a plot that visually represents the selected number in a bar chart.
- reactive data: The plot updates dynamically as the user moves the slider, demonstrating the reactive nature of Shiny outputs.
2.1.3 Interaction with UI and server
So how does the server and UI in Shiny communicate with one another. Here’s an example:
- the user selects a date (
dateInput()
) using an input widget (e.g.input$date
) - in the server, the data is filtered based on the selected date
- a chart is created from the filtered data (
output$chart
) - the chart is displayed in the UI
- the user chooses another date, the cycle repeats: the server processes the new input, and the output updates accordingly
2.2 Creating inputs
2.2.1 UI: adding inputs
All inputs in Shiny are created in the UI using specific input functions, such as selectInput()
. Inputs are typically placed in the sidebar.
- the first argument of any input function is a
name
, which is used to reference the input later - the value of this input is stored in the list of inputs as an object with that name (e.g.
input$name
) - the value updates whenever the user changes the input
You can use additional arguments in the input function to customise options, set header text, and allow for multiple selections.
Here’s an example of using selectInput()
:
sidebarPanel(
selectInput("fruit", # Input name (used to reference the input)
"Choose a fruit:", # Label for the input
choices = c("Apple", "Banana", "Cherry")) # Options
- the input is named
fruit
- the user sees the label
Choose a fruit
- the user can select one fruit from the provided options:
Apple
,Banana
orCherry
This setup allows you to easily reference the selected fruit using input$fruit
in the server logic.
2.2.2 Server: adding inputs
In the server, you can access named input objects using their names in the format input$name
. Depending on the type of input, this will either be a single value or a vector. For example, inputs that allow selecting a range or multiple options will return a vector.
Input values can only be used inside the server within reactive environments. You can treat these input values like any other named variable, such as using them in functions like dplyr::filter()
.
Here’s an example of how to use the input value in the server:
# Reactive expression to filter data based on selected fruit
filtered_data <- reactive({
data %>%
# the data is filtered based on the user input
dplyr::filter(fruit == input$fruit)
})
- the input value
input$fruit
is used to filter the data - a reactive expression
filtered_data
is created to update whenever the input changes
Note: At this point, we haven not touched on the output part yet. Users are currently only filtering the data, but we will explore how to visualise the filtered data in the UI later.
2.2.3 Input arguments
The name is always the first argument for any input function. This must be a simple string that contains only letters, numbers, and underscores. The name must be unique, and you can only display each input once in the UI.
The next argument is the label, this is the human-readable name you see in the UI, and this doesn’t need to be unique.
Different inputs then have a range of different arguments. For example, the arguments in the sliderInput()
function include the following:
min
andmax
: minimum and maximum values for the slider to showvalue
default: value for the slider when it is loadedstep
: increments the slider can move byround
: should the shown values be rounded?pre
andpost
: prefixes and suffixes for numbers (e.g. £ or %)
You can check the arguments for any input in the help (using ?sliderInput
).
2.2.4 Example
Example of the UI:
selectInput("year", label = "Select year(s) of interest",
choices = c(seq(2000, 2020)),
selected = 2020,
multiple = TRUE)
Example of the Server:
2.2.5 Exercise
07:00
- Check out the help for the
selectInput
, what different arguments are available for this input?
Hint: You use ?selectInput
in the R console to bring up the help for this function.
Open Chapter 2 > Exercise-2.2.4
in the training repository and complete the following tasks:
- Identify an input widget already present in your app. Can you change the title as it appears to users?
- Change the input minimum, maximum and default values. How does this change the widget in the UI?
2.2.4 Solution
UI
library(shiny)
shinyUI(fluidPage(
titlePanel("My Shiny App!"),
sidebarLayout(
sidebarPanel(
### Question 2. Identify the input widget and change the title
### Question 3. Change the min, max and default values
sliderInput("bins",
"Slide the button to adjust the value:",
min = 10,
max = 100,
value = 50)
),
mainPanel(
tabsetPanel(
tabPanel("Chart",
plotOutput("distPlot")
),
tabPanel("Text",
h2("Welcome to My Shiny App"),
p("This app demonstrates a simple histogram of waiting times for eruptions of the Old Faithful geyser."),
p(strong("Adjust the number of bins"), " using the slider to see the effect on the chart.")
),
id = "tabs", selected = "Chart"
)
)
)
))
Server
library(shiny)
shinyServer(function(input, output) {
output$distPlot <- renderPlot({
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
})
Training repository: Go to Chapter 2
> Exercise-2.2.4
> Solution
Run either the ui.R
or server.R
file to see the app in action.
2.3 Different input types
There is an input for every data type, such as:
Function | Input type |
---|---|
actionButton() |
Action button |
checkboxGroupInput() |
A group of check boxes |
checkboxInput() |
A single check box |
dateInput() |
A calendar to aid date selection |
dateRangeInput() |
A pair of calendars for selecting a date range |
fileInput() |
A file upload control wizard |
helpText() |
Help text that can be added to an input form |
numericInput() |
A field to enter numbers |
radioButtons() |
A set of radio buttons |
selectInput() |
A box with choices to select from |
sliderInput() |
A slide bar |
submitButton() |
A submit button |
textInput() |
A field to enter text |
Check out the Shiny widgets gallery, and the shiny widget library for more (pretty) input options.
2.3.1 Choose an option
Giving users options to select from is the most common input choice. For a list of options, you can use either dropdown menus or radio buttons:
selectInput("select", label = "Select box",
choices = list("Choice 1" = 1, "Choice 2" = 2),
selected = 1)
- Radio buttons are most suitable for short lists, while dropdowns are more suitable for longer options.
- For either option, you can pass a named vector or list to the “choices” argument, allowing you to make the display name different to the return name e.g.
choices = list("one" = 1, "two" = 2)
will show the user “one”, “two” but return 1, 2, allowing you to filter numerical values. - You can also set
multiple = TRUE
for dropdowns only, which allows the user to select multiple elements.
2.3.2 Checkboxes
Allowing users to choose from a single checkbox or multiple
checkboxGroupInput("checkGroup",
label = h3("Checkbox group"),
choices = list("Choice 1", "Choice 2", "Choice 3"),
selected = 1)
- Checkbox inputs are ideal for simple true/false questions (e.g. show/hide) and output a logical. You can set the default using the “
value
” option. - Checkbox group inputs are used for multiple choice options. Users can select one or more of the options (or none), and the values are returned as a vector server-side.
2.3.3 Free text input
Free text boxes will allow users to input any text they like.
- The standard text input is just a single line of text, while you can specify the number of rows the text box takes up for
textAreaInput
. - There is “
placeholder
” argument which allows you to add an instruction to the user in the box (e.g. “Write value between 10-20 here”)
2.3.4 Numeric inputs
You can provide either numeric boxes or sliders to allow users to provide numeric values.
- You can specify a minimum and maximum for either input type
- Sliders can either provide a single value or a range between two points. To allow users to select a range, provide a vector of two starting values to the “
value
” argument
2.3.5 Exercise
15:00
Open Chapter 2 > Exercise-2.3.5
in the training repository and complete the following tasks:
- In your app, add a dropdown menu to allow people to select a year between 2000 and 2010 including the following:
- Add it to the sidebar of your app
- Give it a sensible title
- Set the default to 2007
- Adjust the parameters of your dropdown menu to allow people to select multiple options.
Bonus
- Try adding a date selector to your app. If you need help to use the parameters, bring up the help using
?dateInput()
.
2.3.5 Solution
UI
library(shiny)
shinyUI(fluidPage(
titlePanel("My Shiny App with Custom Inputs"),
# Layout with sidebar and main panel
sidebarLayout(
# Sidebar panel for inputs
sidebarPanel(
### Question 1 & 2: Dropdown menu to select a year
# - Add a dropdown menu to allow people to select a year between 2000 and 2010.
# - Add it to the sidebar of your app.
# - Give it a sensible title.
# - Set the default to 2007.
# - Adjust the parameters to allow people to select multiple options.
##START HERE ---
selectInput("year", #input name
label = "Select Year(s):",
choices = 2000:2010,
#make 2007 the default value
selected = 2007,
#allow multiple selections
multiple = TRUE), #always remember the comma to separate inputs
### Bonus: Add a date selector to your app
# - Use ?dateInput() to learn more about the parameters
##START HERE ---
dateInput("date",
label = "Choose a date:",
value = Sys.Date() #default value is today's date
)
),
# Main panel for displaying outputs
mainPanel(
)
)
))
Server
Training repository: Go to Chapter 2
> Exercise-2.3.5
> Solution
Run either the ui.R
or server.R
file to see the app in action.
2.4 Creating outputs
2.4.1 Server: adding outputs
In the server, outputs are created using render functions, like renderPlot({})
. The code that generates the output is placed inside both round and curly brackets in this function. Each output type has its own render function (e.g., renderTable
for tables), so it is important to use the correct one for the output you want.
The render function is assigned as an object in the output list, for example, output$table <- renderTable({})
, where the name following $
is the output name.
Outputs created in the server will not appear in the app until it is referenced in the UI.
2.4.2 UI: adding outputs
You can display named output objects in the UI by referencing their names in an output function (e.g., plotOutput()
). Just like with render functions in the server, the output function in the UI must match the type of output being displayed (e.g., plotOutput()
for charts, tableOutput()
for tables).
Output functions are usually placed inside the main panel of the UI, and the output appears where the function is located. The first argument in an output function is the name of the output in the output list, passed as a string.
2.5 Different output types
2.5.1 Text outputs
Text outputs can be created to update dynamically in response to changing variables. This can be combined with a paste()
or paste0()
function to create dynamic messages and titles.
UI:
Server:
# the output name is called "title", the same as the input in the UI
output$title <-
renderText({paste0("Date for", lubridate::today(), "(GB)")
})
- Uses the
renderText({})
function in the server, containing code to create the text itself. This is assigned to a named object in the output list. - Uses the
textOutput()
function in the UI, with the output name passed as the first argument.
Remember the output will not appear to the user, until the name title
is referenced in both the UI and server.
2.5.2 Plot outputs
Chart outputs can be created to update dynamically in response to changing variables (e.g. show only today’s data).
UI:
Server:
# the output name is called "plot", the same as the input in the UI
output$plot <-
renderPlot({ggplot(mtcars, aes(x = qsec, y = disp)) +
geom_point()
})
- For ggplot charts, use the
renderPlot({})
function in the server, containing code to create the chart. This is assigned to a named object in the output list. - Uses the
plotOutput()
function in the UI, with the output name passed as the first argument.
2.5.3 Table outputs
You can display tables of data in Shiny presenting any data frame object as a data table. These have a high level of inbuilt functionality, so users can search and order the table without you needing to change anything. You can also add buttons to allow data download in a variety of formats.
UI:
Server:
# the output name is called "table", the same as the input in the UI
output$table <-
renderDataTable({mtcars})
- Uses the
renderDataTable({})
function in the server, containing code to create the text itself. This is assigned to a named object in the output list. - Uses the
dataTableOutput()
function in the UI, with the output name passed as the first argument.
2.5.4 Summary table
Output type | Render function (in the server) | Output function (in the UI) | Usage |
---|---|---|---|
Text | renderText({}) |
textOutput() |
Display text |
Table | renderTable({}) |
tableOutput() |
Display data in a table format |
Plot/chart | renderPlot({}) |
plotOutput() |
Display static charts |
UI elements | renderUI({}) |
uiOutput() |
Dynamically generate UI components |
Image | renderImage({}) |
imageOutput() |
Display static or dynamic images |
HTML/Markdown | renderText({}) |
htmlOutput() |
Display HTML or Markdown content (use inline HTML) |
DataTable | renderDataTable({}) |
dataTableOutput() |
Interactive, sortable tables (via the DT package) |
Plotly plot/chart | renderPlotly({}) |
plotlyOutput() |
Interactive, dynamic charts created with plotly |
Leaflet map | renderLeaflet({}) |
leafletOutput() |
Interactive maps created with leaflet |
Value box | renderValueBox({}) |
valueBoxOutput() |
Display a summarised value in a visually appealing box (via shinydashboard ) |
Text area | renderPrint({}) |
verbatimTextOutput() |
Display raw text output like logs or model summaries |
2.6 Exercise
15:00
Open Chapter 2 > Exercise-2.6
in the training repository and complete the following tasks:
- Create a text output which says “Welcome to the app. Today’s date is”, and then gives the user the current date (Hint: you can use the
lubridate::today()
function to get this value). - Call the library
gapminder
in your app (Hint:library(gapminder)
) - Load the
gapminder
dataset into your app (Hint:gapminder::gapminder
). Display the raw data as a data table in your app (Hint:dataTableOutput()
in the UI andrenderDataTable({})
in the server). - Try exploring the native functionality of the data table format. Can you see how to order and search the data?
2.6 Solution
UI
library(shiny)
### Question 1 & 2: Call the libraries - gapminder and lubridate
##START HERE ----
library(lubridate)
library(gapminder)
shinyUI(fluidPage(
titlePanel("My Shiny App"),
sidebarLayout(
sidebarPanel(
selectInput("year",
label = "Select Year(s):",
choices = 2000:2010,
selected = 2007,
multiple = TRUE
)
),
mainPanel(
### Question 1: Create text output that says
## "Welcome to the app. Today's Date is",
## use the lubridate::today() to get today's date
##START HERE ----
# Hint: textOutput() - use appropriate input `name`
textOutput("welcomeText"),
### Question 3: Display the gapminder dataset into the app
##START HERE ----
# Hint: dataTableOutput() - use appropriate input `name`
dataTableOutput("gapminderTable")
)
)
))
Server
library(shiny)
### Question 1 & 2: Call the libraries - gapminder and lubridate
##START HERE ----
library(lubridate)
library(gapminder)
shinyServer(function(input, output) {
### Question 1: Create text output for the welcome message
# Hint:
## - renderText()
## - paste() to create the desired text
## - remember the output$`name` needs to be the same as the input `name` in the UI
## START HERE ----
output$welcomeText <- renderText({
paste("Welcome to the app. Today's date is", lubridate::today())
})
### Question 3: Display the gapminder dataset
# Hint:
## - renderDataTable()
## - use gapminder::gapminder inside the renderDataTable
## - remember the output$`name` needs to be the same as the input `name` in the UI
## START HERE ----
output$gapminderTable <- renderDataTable({
gapminder::gapminder
})
})
Training repository: Go to Chapter 2
> Exercise-2.6
> Solution
Run either the ui.R
or server.R
file to see the app in action.
2.7 Building reactivity
2.7.1 Putting inputs and outputs together
To get true interactivity in your apps, you can use input values to feed into outputs. Input values can be used exactly like any other named object in a list, called using input$name
inside an output render call. Shiny refreshes outputs automatically every time a linked input is updated.
UI:
# "cylinders" is from input$cylinders
selectInput("cylinders",
"Select number of cylinders",
choices = c(2, 3, 4))
# "table" from output$table
dataTableOutput("table")
Server:
2.7.2 Reactivity: reactive text
Combine input widgets with text outputs to create text which updates in response to changing user input. This can be used to produce commentary and titles which update to include e.g. filter variables such as region or year.
UI:
# "region" is from input$region
selectInput("region",
label = "Choose region",
choices = c("North", "South", "East", "West"))
# "title" is from output$title
textOutput("title")
Server:
2.7.3 Reactivity: reactive chart
Like to reactive text, chart and other output objects can be filtered using user inputs to create outputs specific to user interests.
UI:
# "region" is from input$region
selectInput("region",
label = "Choose region",
choices = c("North", "South", "East", "West"))
# "plot" is from output$plot
plotOutput("plot")
Server:
2.7.4 Exercise
15:00
Open Chapter 2 > Exercise-2.7.4
in the training repository and complete the following tasks:
- Call the library
dplyr
in your app - Filter the data in the table output on the year chosen in the input, so that a user can select what year of data they would like to display. (Note: not all the years in your selector are in the
gapminder
dataset!) - Does your data table filter still work if the user selects multiple years?
Bonus
- Update your dynamic text output so it says “Welcome to the app. Showing data for:” and then the year the user has chosen. Can you make this work for multiple years too?
1.9 Solution
UI
library(shiny)
library(lubridate)
library(gapminder)
### Question 1. Call the library: dplyr
##START HERE ----
library(dplyr)
shinyUI(fluidPage(
titlePanel("My Shiny App"),
sidebarLayout(
sidebarPanel(
selectInput("year",
label = "Select Year(s):",
### Question 2.
# Hint: not all years in your selector are in the gapminder dataset
# - use unique(gapminder$year) to view the new years
##START HERE ----
# - Change the year choices based on the gapminder dataset
choices = unique(gapminder$year),
selected = 2007,
multiple = TRUE
)
),
mainPanel(
textOutput("welcomeText"),
dataTableOutput("gapminderTable")
)
)
))
Server
library(shiny)
library(lubridate)
library(gapminder)
### Question 1. Call the library: dplyr
##START HERE ----
library(dplyr)
shinyServer(function(input, output) {
### Question 4 (Bonus): Update the text output so it says
## "Welcome to the app. Showing data for:" and the year(s) the user has chosen
## Hint:
# - Use a 2nd paste() with collapse = "," to format multiple years as a single string
output$welcomeText <- renderText({
##START HERE ----
paste("Welcome to the app. Showing data for", paste(input$year, collapse = ", "))
})
### Question 2 & 3: Filter `dplyr::filter()` the gapminder dataset by the chosen year(s)
## Hint:
# - Write the new code inside the renderDataTable function
# - Remember use are using the input$`year` from the selectInput in the UI
output$gapminderTable <- renderDataTable({
gapminder::gapminder %>%
##START HERE ----
dplyr::filter(year %in% input$year)
})
})
Training repository: Go to Chapter 2
> Exercise-2.7.4
> Solution
Run either the ui.R
or server.R
file to see the app in action.
This is a good opportunity to take a 45-minute to an hour lunch break away from the computer to refresh your mind, stretch, and reset before continuing onto Chapter 3.