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).

sliderInput("num", 
            "Choose a number:",
            min = 1, 
            max = 100, 
            value = 25)

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:

  1. the user selects a date (dateInput()) using an input widget (e.g. input$date)
  2. in the server, the data is filtered based on the selected date
  3. a chart is created from the filtered data (output$chart)
  4. the chart is displayed in the UI
  5. 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 or Cherry

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 and max: minimum and maximum values for the slider to show
  • value default: value for the slider when it is loaded
  • step: increments the slider can move by
  • round: should the shown values be rounded?
  • pre and post: 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:

reactive({
  jr_dataframe %>%
    dplyr::filter(Year %in% input$year)
})

2.2.5 Exercise

07:00

  1. 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:

  1. Identify an input widget already present in your app. Can you change the title as it appears to users?
  2. 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)

radioButtons(“radio", label = “Radio buttons", 
    choices = list("Choice 1", "Choice 2", "Choice 3"))
  • 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

checkboxInput("checkbox", 
              label = "Choice A", 
              value = TRUE)

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.

textInput("name", "What's your name?")

textAreaInput("story", "Tell me about yourself", rows = 3)
  • 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.

numericInput("num", 
             "Numeric input", 
             value = 1, 
             min = 0, 
             max = 100)

sliderInput("rng", 
            "Slider range", 
            value = c(25, 75), 
            min = 0, 
            max = 100)
  • 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:

  1. 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
  1. Adjust the parameters of your dropdown menu to allow people to select multiple options.

Bonus

  1. 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

library(shiny)

shinyServer(function(input, output) {
  
})

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:

# input name is called "title"
textOutput("title") 

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:

# the input name is called "plot"
plotOutput("plot")

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:

# the input name is called "table"
dataTableOutput("table")

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:

  1. 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).
  2. Call the library gapminder in your app (Hint: library(gapminder))
  3. 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 and renderDataTable({}) in the server).
  4. 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:

# table from the dataTableOutput("table")
output$table <- 
  renderDataTable({
    mtcars %>% 
      # input$cylinders is from the selectInput("cylinders")
      dplyr::filter(cyl == input$cylinders) 
    })

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:

# title from textOutput("title")
output$title <- 
  renderText({
    # input$region is from selectInput("region)
    paste0("Date for" lubridate::today(), input$region)}) 

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:

# plot is from plotOutput("plot")
output$plot <- renderPlot({ 
  
    data <- mtcars %>%
      # input$region from selectInput("region")
        dplyr::filter(region == input$region) 
    
    ggplot(data, aes(x = qsec, y = disp))+
      geom_point()
    })

2.7.4 Exercise

15:00

Open Chapter 2 > Exercise-2.7.4 in the training repository and complete the following tasks:

  1. Call the library dplyr in your app
  2. 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!)
  3. Does your data table filter still work if the user selects multiple years?

Bonus

  1. 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.