Focus points

  • Getting started with Shiny
  • Understanding reactivity
  • Designing UI
  • Dashboards

Why use Shiny?

  • Shiny's really useful if we have R script or visualization that we want to make interactive.
  • Shiny apps can be hosted online and be shared with the world easily if we use a hosting platform like shinyapps.io
  • There is no need to learn HTML or JavaScript.

shinyApp Arguments: ui & server

  • The ui argument contains "client-side code"---what appears in the web browser \(\Rightarrow\) defining our slider input.
  • The meat of your app is in the server argument---where evaluation happens.
    • The function that used inside of the ShinyApp:
      * render* functions are used to generate output objects in the server---and are dependent on input$vars.
      * Output* functions are used to display output objects in the client---and must be given strings.

Basic framework

# shiny basic framework
library(shiny)
shinyApp(
  ui = ,
  server = function(input, output){

  }
)
Anatomy of Shiny Apps (from Mine Çetinkaya-Rundel, Shiny Short Course)

Anatomy of Shiny Apps (from Mine Çetinkaya-Rundel, Shiny Short Course)

Shiny example 1

The Shiny app below allows us to visualise the expression \(x^n\) with n ranging from 1 to 5:

# shiny example 1
shinyApp(
  ui = fluidPage(
    sliderInput(inputId = "exponent",
                label = "Exponent",
                min = 1,
                max = 5,
                value = 2),
    plotOutput("curvePlot")
  ),
  server = function(input, output){
    output$curvePlot <- renderPlot(
      curve(x^input$exponent, from = -5, to = 5)
    )
  }
)

Different control types

ShinyApp Widget Gallery Website

ShinyApp Widget Gallery Website

  • selectInput produces strings---which may need to be converted (or coerced) into other data types
  • Use print(input$vars) which vars is the input ID inside of render* functions to debug and troubleshoot

Shiny example 2

shinyApp(
  ui = fluidPage(
    selectInput("exponent", label = "Exponent",
                choices = 1:5,
                selected = 2),
    plotOutput("curvePlot")
  ),
  server = function(input, output){
    output$curvePlot <- renderPlot(
      print(input$exponent) # to see the error of selectInput turning to String. Therefore, (below)
      curve(x^as.numeric(input$exponent), from = -5, to = 5) # (from above) we must use as.numeric here
    )
  }
)

Interdependent controls

i.e. the controls which are dependent on one another.

  • UI elements---unaware of the values of all the other UI elements
  • Use renderUI to create a slider dependent on server-side values
  • Display UI elements with uiOutput

  • UI elements are displayed before their values are set, which can lead to errors. Thus, \(\Longrightarrow\) To prevent output being shown before UI elements have values

if (is.null(input$xlim)){
   return()
}

Shiny example 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
shinyApp(
  ui = fluidPage(
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    sliderInput("xlim", label = "xlim",
                min = 5,
                max = 10,
                value = 10),
    plotOutput("histogram")
  ),
  server = function(input, output){
      output$histogram <- renderPlot(
      hist(rnorm(input$no_data, mean = input$mean, sd = input$sd), xlim = c(-input$xlim,input$xlim))
    )
  }
)

How to make xlim dependent? \(\Rightarrow\) We need to define the slider input inside of the server, because it's only inside of the server that your input variables exist: - decrease the size of the console, move the slider input (above, line 15-18) and add UI Output.
- output to our UI a slider which is generated service side \(\Rightarrow\) xlim_slider; inside of our server function, need to define a new output object which we'll call xlim slider, we'll assign that as the rendering of the UI object and that UI object (below, line 26-30). - prevent the errors being shown before UI elements have values (below, line 22-24).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
shinyApp(
  ui = fluidPage(
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    uiOutput("xlim_slider"), # connect to line below
    plotOutput("histogram")
  ),
  server = function(input, output){
    
    output$xlim_slider <- renderUI({ # connect to line above
      
      if(is.null(input$mean)){ # if mean does not yet have a value i.e. input$mean is null then return an empty element. If it's not the case then return the slider
        return()
      }
      
      sliderInput("xlim", label = "xlim",
                  min = input$mean, # instead put 5 we change such that mean def. above
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      hist(rnorm(input$no_data, mean = input$mean, sd = input$sd), xlim = c(-input$xlim,input$xlim))
    })
  }
)

Reactive Expressions

  • Using the Reactive Expressions when we want to control the evaluation order within a Shiny app, when we want to stop an expression being updated until we've used a specific controller.

Shiny example 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
shinyApp(
  ui = fluidPage(
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    uiOutput("xlim_ui"),
    plotOutput("histogram") # we're outputting is plotOutput of histogram, which in server function in lines below
  ),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      
      if(is.null(input$mean)){
        return()
      }
      
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    output$histogram <- renderPlot({ # having the output variable histogram which simply renders a plot of data generated from function rnorm below
      if(is.null(input$xlim)){
        return()
      }
      hist(rnorm(input$no_data, mean = input$mean, sd = input$sd), xlim = c(-input$xlim,input$xlim))
    })
  }
)
  • If we move controller, the data is getting re-generated. As a consequence, every time we change the control, we're getting a new data set generated. And the reason for that is the entire expression, which we see here, on line 39, is being reevaluated each time every controller is called. \(\Rightarrow\) we need to use a reactive expression to control your evaluation (starting at below, line 31-33; then call the reactive expression in line 40).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
shinyApp(
  ui = fluidPage(
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    uiOutput("xlim_ui"),
    plotOutput("histogram")
  ),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      if(is.null(input$mean)){
        return()
      }
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    data_for_plot <- reactive({
      rnorm(input$no_data, mean = input$mean, sd = input$sd)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      
      data_for_plot <- data_for_plot() # we call the reactive expression as a function ==> data won't be updated unless these variables mean, sd are changed
      
      hist(data_for_plot, xlim = c(-input$xlim,input$xlim))
    })
  }
)
  • reactive* expression are "invalidated" when input$vars is updated. Thus, reactive expressions are only evaluated when input variables within them are updated.

  • Separating your code into separate reactive expressions ensures control over what is updated when we have an expensive calculation or because we are just changing the plot range for visualization, and we don't want the underlying data to change as a result of changing that variable.

  • Reactive objects must be called as a function.

When to use reactives

  • By using a reactive expression for the subsetted data frame, we were able to get away with subsetting once and then using the result twice
  • In general, reactive conductors let us
    • not repeat yourself (i.e. avoid copy-and-paste code) which is a maintenance boon
    • decompose large, complex (code-wise, not necessarily CPU-wise) calculations into smaller pieces to make them more understandable
  • These benefits are similar to what happens when you decompose a large complex R script into a series of small functions that build on each other

More example about the reactive()

Suppose we want to plot only a random sample of movies, of size determined by the user. What is wrong with the following?

# Server
# Create a new data frame that is a sample of n_samp
# observations from movies
movies_sample <- sample_n(movies_subset(), input$n_samp)
# Plot the sampled movies
output$scatterplot <- renderPlot({
   ggplot(data = movies_sample, 
          aes_string(x = input$x, y = input$y, color = input$z)) + 
          geom_point(…)
})

\(\Longrightarrow\) The solution would be:

# Server
# Create a new data frame that is a sample of n_samp
# observations from movies
movies_sample <- reactive({
   req(input$n_sample) # ensure availability of value
   sample(movies_sample(), input$n_sample)
})

# Plot the sampled movies
output$scatterplot <- renderPlot({
   ggplot(data = movies_sample(), # need the () 
          aes_string(x = input$x, y = input$y, color = input$z)) + 
          geom_point(…)
})

Render functions

render*({ [code_chunk] })

  • These functions make objects to display
  • Results should always be saved to output$
  • They make an observer object that has a block of code associated with it
  • The object will rerun the entire code block to update itself whenever it is invalidated
render*({ [code_chunk] }) (Shiny cheatsheet)

render*({ [code_chunk] }) (Shiny cheatsheet)

Layout Shiny apps

There are 3 different layout options available:

  • sidebarLayout: two column layout with responsiveness so as we move them to a mobile device rather than having two columns, we have one column which is the left hand column above the right. And this is always mapped inside of a fluidpage.

  • tabsetPanel: a sidebar layout with a number of different tabs in the right hand column. This is wrapped inside of a fluidpage.

  • navbarPage: to design multi-page applications or dashboards using Shiny App. A navbarPage is a navbarPage and not a fluidPage.

With shiny apps, it's possible to build beautiful responsive designs using numeral different layout options.

With shiny apps, it's possible to build beautiful responsive designs using numeral different layout options.

fluidPage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
library(shiny)
shinyApp(
  ui = fluidPage( #define with fluidPage function here
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    uiOutput("xlim_ui"),
    plotOutput("histogram")
  ),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      if(is.null(input$mean)){
        return()
      }
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    data_for_plot <- reactive({
      rnorm(input$no_data, mean = input$mean, sd = input$sd)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      
      data_for_plot <- data_for_plot()
      
      hist(data_for_plot, xlim = c(-input$xlim,input$xlim))
    })
  }
)

sidebarPanel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
library(shiny)
shinyApp(
  ui = fluidPage(
    sidebarLayout( #add sidebarLayout
      sidebarPanel(
    sliderInput("no_data", label = "Number of data",
                min = 1000,
                max = 5000,
                value = 1000),
    sliderInput("mean", label = "Mean",
                min = 0,
                max = 8,
                value = 3),
    sliderInput("sd", label = "Standard Deviation",
                min = 1,
                max = 10,
                value = 2),
    uiOutput("xlim_ui")
      ),
    mainPanel(plotOutput("histogram")) #add mainPanel function outer of plotOutput
    )
  ),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      if(is.null(input$mean)){
        return()
      }
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    data_for_plot <- reactive({
      rnorm(input$no_data, mean = input$mean, sd = input$sd)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      
      data_for_plot <- data_for_plot()
      
      hist(data_for_plot, xlim = c(-input$xlim,input$xlim))
    })
  }
)

tabsetPanel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
library(shiny)
shinyApp(
  ui = fluidPage( 
    sidebarLayout( #add sidebarLayout
      sidebarPanel(
        sliderInput("no_data", label = "Number of data",
                    min = 1000,
                    max = 5000,
                    value = 1000),
        sliderInput("mean", label = "Mean",
                    min = 0,
                    max = 8,
                    value = 3),
        sliderInput("sd", label = "Standard Deviation",
                    min = 1,
                    max = 10,
                    value = 2),
        uiOutput("xlim_ui")
      ),
      mainPanel(tabsetPanel( #add mainPanel function outer of tabsetPanel/plotOutput
        tabPanel("Histogram",
                 plotOutput("histogram")),
        tabPanel("Data Table",
                 dataTableOutput("data_table"))
      )
    )
  )),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      if(is.null(input$mean)){
        return()
      }
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    data_for_plot <- reactive({
      rnorm(input$no_data, mean = input$mean, sd = input$sd)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      
      data_for_plot <- data_for_plot()
      
      hist(data_for_plot, xlim = c(-input$xlim,input$xlim))
    })
    
    output$data_table <- renderDataTable({
      as.data.frame(data_for_plot())
    })
  }
)

Themes and customization

Shiny Themes in RStudio website

Shiny Themes in RStudio website

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
library(shiny)
library(shinythemes) # load package here
shinyApp(
  ui = navbarPage(
    theme = shinytheme("journal"), #add shiny theme syntax here
    "Dashboard-like navbarPage",
    tabPanel("Histograms",
    fluidPage(sidebarLayout(
      sidebarPanel(
        sliderInput("no_data", label = "Number of data",
                    min = 1000,
                    max = 5000,
                    value = 1000),
        sliderInput("mean", label = "Mean",
                    min = 0,
                    max = 8,
                    value = 3),
        sliderInput("sd", label = "Standard Deviation",
                    min = 1,
                    max = 10,
                    value = 2),
        uiOutput("xlim_ui")
      ),
      mainPanel(plotOutput("histogram"))
    ))
    ),
    navbarMenu(
      "Menu Item With Children",
      tabPanel("Child One",
                fluidPage(
                  plotOutput("curve1")
                )
               ),
      tabPanel("Child Two",
               fluidPage(
                plotOutput("curve2")
               )
               )
    ), collapsible = TRUE
  ),
  server = function(input, output){
    
    output$xlim_ui <- renderUI({
      if(is.null(input$mean)){
        return()
      }
      sliderInput("xlim", label = "xlim",
                  min = input$mean,
                  max = 10,
                  value = input$mean,
                  step = 1)
    })
    
    data_for_plot <- reactive({
      rnorm(input$no_data, mean = input$mean, sd = input$sd)
    })
    
    output$histogram <- renderPlot({
      if(is.null(input$xlim)){
        return()
      }
      
      data_for_plot <- data_for_plot()
      
      hist(data_for_plot, xlim = c(-input$xlim,input$xlim))
    })
    
    output$curve1 <- renderPlot({
      curve(x^1, from = -5, to = 5)
    })
    
    output$curve2 <- renderPlot({
      curve(x^2, from = -5, to = 5)
    })
  }
)

Why use shinyapps.io?

  • shinyapps.io is a hosting platform for Shiny apps with deep integration into the RStudio platform.
  • While displayed onscreen, Shiny apps consume active hours
  • There are a default timeout period of 15 mins for our apps hosted on shinyapps.io
Interface of Shiny.io

Interface of Shiny.io

\(\Rightarrow\) Connect RStudio to shinyapps.io: - register for an account at shinyapps.io
- a free shinyapps.io account allows 25 active hours
- connect RStudio to shinyapps.io account through the Publishing section of the Global Options screen

\(\Rightarrow\) Deploying Presentations to shinyapps.io:
- ensure the preamble includes "runtime: shiny"
- "Run Presentation" \(\rightarrow\) "Publish"

\(\Rightarrow\) Manage the deployed shinyapps.io:
- must use the shinyapps.io dashboard
- track engagement and remaining active hours through the dashboard
- archive and then delete Shiny apps as necessary

Why make dashboards with Shiny?

  • Shiny dashboards allow your analysis, visualization, and deployment code to coexist seamlessly
  • Dashboards built with Shiny are beautifully responsive -- thanks to the Bootstrap framwork
  • Rich, interactive charts and maps can easily be included using htmlwidgets

ui.R and server.R files

Self-Contained shinyApp vs. Split-File Shiny Apps

Self-Contained shinyApp vs. Split-File Shiny Apps

shinyApp is useful small, self-contained Shiny apps: when we start to have large script files, it's very difficult to understand what's going on in them. \(\Rightarrow\) separating out our ui and our server components into different files, it's much easer for us to build large-scale applications and to separate our concerns.

It's also useful to note that split-file Shiny apps can be more readily reused in other content than self-contained shinyApps.

It's much easier to deploy a split-file Shiny app to the shinyapps.io service than it is a self-contained shinyApp.

Embed htmlwidgets, shinyAppDir & iframes in Shiny apps

Calling the Movie Browser App

runGitHub

runGitHub("movie-browser-app","seajanelamo")
  • Source code refer to: github

References