Web app development with Shiny

This tutorial will introduce you to the basics of web app development using r-shiny. This notebook is best used in conjunction with the recorded delivery of the training session which is available on https://youtu.be/5klSpGC2puU and the Advanced R presentation available in the https://gitlab.com/SManzi/r-for-healthcare-training.

Code structure

Three main components:

  • The app layout (ui)
  • The app functionality (server)
  • The app run command (shinyApp(ui, server))

App layout

We start by defining the user interface in terms of the page type and the components to navigate, input data and output data

ui <- <page type>(
       <Navigation components>
       <Input components>
       <Output components>
   )

Let’s have a look at the ’layout_example’ app

library(shiny)

# Define the page layout type ----
ui <- fluidPage(
  # App title ----
  titlePanel("My first web app"),
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    # Sidebar panel for inputs ----
    sidebarPanel(
      h3("A sidebar panel"),
      p("In the side bar I can place
        all of my input selection options
        for what I want to happen in the
        main panel")
    ),
    # Main panel for displaying outputs ----
    mainPanel(
      # Outputs to be displayed
      h3("The main panel"),
      p("In the main panel I would place
        all of my outputs which change
        given the inputs in the sidebarPanel")
    )
  )
)
# Define server logic which is how the inputs will be used to produce the outputs ----
server <- function(input, output){}
# Create Shiny app ----
shinyApp(ui=ui, server=server)
  • titlePanel: title block
  • sidebarLayout: Defines a layout with a left aligned panel and a main panel
  • sidebarPanel: Container for the sidebarPanel components
  • h3: Header component
  • p: paragraph component
  • mainPanel: Container for the mainPanel components

App layout – tabs

Tabs are a useful way to organise an app to simplify the presentation of components
A tabsetPanel() is first initiated in the layout within either a sidebarPanel or a mainPanel as the main tab component container.
tabPanel() components are then added within the tabsetPanel container. These are the individual tab containers.
Within each tabPanel you then add the components you want displayed within a given tab
Let’s have a look at the ’tabset basic example’ to see how this is implemented in practice

library(shiny)

# Define the page layout type ----
ui <- fluidPage(
  # App title ----
  titlePanel("Tabsets"),
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    
    # Sidebar panel for inputs ----
    sidebarPanel(
      h3("A sidebar panel"),
      p("In the side bar I can place
        all of my input selection options
        for what I want to happen in the
        main panel")
    ),
    
    # Main panel for displaying outputs ----
    mainPanel(
      
      # Output: Tabset w/ plot, summary, and table ----
      tabsetPanel(type = "tabs",
                  tabPanel("Plot", p("Here I could place some plots perhaps")),
                  tabPanel("Summary", p("Here I might have some summary statistics")),
                  tabPanel("Table", p("This might be a table of the raw data"))
      )
      
    )
  )
)

# Define server logic which is how the inputs will be used to produce the outputs ----
server <- function(input, output) {}

# Create Shiny app ----
shinyApp(ui, server)

App functionality

Define the use of the input components to produce outputs to pass to the output components
Generic functionality template

server <- function(input, output){
            <output component> <- <render type>({
                <R expression using input component value>})
            <function name> <- <reactive function>({
                <R expression using input component value>})
    }

Let’s have a look at the ’functionality example’ app

library(shiny)

ui <- fluidPage(
  titlePanel("My first web app"),
  
  sidebarLayout(
    sidebarPanel(
      h3("A sidebar panel"),
      p("In the side bar I can place
        all of my input selection options
        for what I want to happen in the
        main panel"),
    sliderInput("obs",
                "Number of observations:",
                min=0,
                max=1000,
                value=500)
    ),
    mainPanel(
      h3("The main panel"),
      p("In the main panel I would place
        all of my outputs which change
        given the inputs in the sidebarPanel"),
      plotOutput("distPlot")
    )
  )
)

server <- function(input, output){
  output$distPlot <- renderPlot({hist(rnorm(input$obs))})
}

shinyApp(ui=ui, server=server)
  • Layout
    • plotOutput: output container of specific type; unique id required
  • functionality
    • server is a function to bring in and pass out input and output objects of components
    • output $ ’component_id’: defines object to pass output to
    • renderPlot: ALL outputs require a render function
    • within a render function, an R expression must be wrapped in curly braces {}
    • input$’component_id’: defines the object from which to get a value

Reactive functions

Reactive functions can be used in a shiny app in the same way as user defined functions in a normal R script
Let’s look at the ’reactive example’ app

library(shiny)

ui <- fluidPage(
  titlePanel("My first web app"),
  
  sidebarLayout(
    sidebarPanel(
      h3("A sidebar panel"),
      p("In the side bar I can place
        all of my input selection options
        for what I want to happen in the
        main panel"),
      sliderInput("obs",
                  "Number of observations:",
                  min=0,
                  max=1000,
                  value=500)
    ),
    mainPanel(
      h3("The main panel"),
      p("In the main panel I would place
        all of my outputs which change
        given the inputs in the sidebarPanel"),
      plotOutput("distPlot"),
      textOutput("distText")
    )
  )
)

server <- function(input, output){
  distDesc <- reactive({paste0("You have selected ", input$obs," observations to plot")})
  output$distPlot <- renderPlot({hist(rnorm(input$obs))})
  output$distText <- renderText({distDesc()})
}

shinyApp(ui=ui, server=server)
  • distDesc: the reactive function name
  • renderText: the output object will be a string object
  • The reactive function can be called from within another function

Importing data

The fileInput() component is used to import data into the app
Let’s look at the ’import example’ app
An example csv file is available in the GitLab repository

library(shiny)

ui <- fluidPage(
  titlePanel("Import data example"),
  
  sidebarLayout(
    sidebarPanel(
      fileInput("upload", "Upload file",
                accept=c("text/csv",
                         "text/comma-separated-values,text/plain",
                         ".csv")
                )
    ),
    mainPanel(
      tableOutput("dataTable")
    )
  )
)

server <- function(input, output){
  my_data <- reactive({
    req(input$upload)
    df <- read.csv(input$upload$datapath)
    data <- df[,1]
    return(data)
  })
  
  output$dataTable <- renderTable({my_data()})
}

shinyApp(ui=ui, server=server)
  • Requires a unique id and a label
  • Can be set to only accept certain file types
  • A reactive function is required to read in the data
  • The first line of the function should be req(input $ ’component_id’)
  • Read in the data using the correct read function
  • Return the data in the required format
  • tableOutput() component used to display the data
  • renderTable() function used to call and produce the table object which is passed to the tableOutput component

Using inputs

There are a varity of different input object types each of which provides you with a specific input object type.

  • Single components produce a single value
    • Single input object of type string, boolean, integer, double
    • A single value can be evaluated directly
  • Groups or range components produce a list of values
    • List of input objects of type string, boolean, integer, double
    • A list of object will need to be evaluated iteratively

App deployment

  • Launch from within R
    • runUrl: host files at a web address
    • runGitHub: host files on GitHub
    • runGist: pasteboard service operated by GitHub not requiring sign-up
  • Launch as a standalone webpage
    • Shinyapps.io: free and paid options, simplest option
    • Shiny server: Free* and paid options, requires a linux server
    • Shiny proxy: Similar to Shiny server but free* and provides enterprise level functionality

* Free in this instance means the containerisation of the app is free but you need to provide a domain name and server space

Exercise

The aim of the exercise is to build an app to automate the distribution fitting process. You will need to use what you know about base-r programming, distribution fitting with fitdistrplus and developing web apps with shiny

App specifications

  • Ability to import data
  • Display the raw data
  • Plot the data as a histogram (note: ggplot2 is not compatible with shiny)
  • Plot the empirical density, cumulative distribution and cullen and frey graph
  • Print the cullen and frey summary statistics
  • Fit multiple named distributions to the data
  • Plot the density, cumulative density, QQ and PP plots of the fitted distributions
  • Print the fitting summaries, goodness of fit statistics and uncertainty statistics

Templates in varying degrees of difficulty are available in the GitLab repository or you can start with a blank script.

An example solution is available here