Programming Shiny Apps

An introduction to Shiny with many examples. QHELP Portugal 2023

Julian Mollenhauer

Universität Tübingen

1 Creating a Shiny app

1.1 Interactive web applications with Shiny

A Shiny app is an interactive web page that is created using R and functions from the Shiny package (and the many packages Shiny depends on).

Getting help

  • “Mastering Shiny” written by Hadley Wickham
  • https://shiny.rstudio.com/
  • https://shiny.rstudio.com/tutorial/
  • https://shiny.rstudio.com/gallery/
  • https://shiny.rstudio.com/articles/
  • https://shiny.rstudio.com/images/shiny-cheatsheet.pdf

1.2 Structure of a Shiny app

A Shiny app consists of

  • a web page (HTML, CSS, JS): the “user interface”
  • an R script that runs on a server

Basic template

library(shiny) # install.packages("shiny")

# create web page
ui <- fluidPage(
  ...
) 

# "server logic"; calculate output from user input
server <- function(input, output){
  ...  
} 

# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)

1.3 Running a Shiny app

Run it locally on your computer

  • For each app, create a new directory

  • Put app.R into this directory

  • Run it from inside R by

    runApp("name-of-app-directory")
  • or use RStudio’s Run App button

Run it on a web server

  • (We will not cover this in this course.)
  • https://shiny.rstudio.com/deploy/

1.4 A simple example

Histogram of random values

library(shiny)
## User interface with slider (input) and plot (output)
ui <- fluidPage(
  sliderInput(inputId = "n", 
              label   = "Number of samples", 
              min = 1, max = 100, value = 10),
  plotOutput(outputId = "hist")
) 

## Server function connecting input and output
server <- function(input, output){
  output$hist <- renderPlot({
    x <- rnorm(input$n) # draw n random values
    hist(x)
  })
}

shinyApp(ui = ui, server = server)

1.5 The user interface

Building blocks of an interactive web page are elements the user interacts with; created by *Input() and *Output() functions.

1.5.1 Inputs

Every input needs a unique inputId, otherwise the reference in the server function (see below) to the entered value is not unique.

sliderInput(inputId = "id1",
            label   = "Slider input example",
            min = 1, max = 10, value = 5)


numericInput(inputId = "id2", 
             label   = "Numeric input example", 
             min = 0, max = 100, value = 30, step = 1)


selectInput(inputId  = "id3", 
            label    = "Select list input example", 
            choices  = c("Choice 1", 
                         "Choice 2", 
                         "Choice 3"), 
            selected = "Choice 2", 
            multiple = TRUE)


checkboxInput(inputId = "id4", 
              label = "Checkbox input example", 
              value = FALSE)

See also checkboxGroupInput() to create a group of checkboxes that can be used to toggle multiple choices independently.


dateInput(inputId = "id5",
          label   = "Date input example", 
          value   = "2020-10-20", 
          min = "2020-01-01", max = "2021-12-31")


fileInput(inputId  = "id6", 
          label    = "File upload example", 
          multiple = FALSE, 
          accept   = ".txt")


radioButtons(inputId  = "id7", 
             label    = "Radio buttons example", 
             choices  = c("Option 1", 
                          "Option 2", 
                          "Option 3"), 
             selected = "Option 3")


textInput(inputId     = "id8",  
          label       = "Text input example", 
          placeholder = "Name")


actionButton(inputId = "id9", 
             label   = "Action button example")

1.5.2 Outputs

Outputs (often) respond to changes of input values. Just like inputs, they need a unique identifier, called outputId.

textOutput(outputId = "myUniqueOutputId")
plotOutput()         # Plot output element
textOutput()         # Text output element
verbatimTextOutput() # Verbatim text output element
tableOutput()        # Table output element
dataTableOutput()    # Data table output element
uiOutput()           # User interface (html) element
downloadButton()     # Download button
outputOptions()      # Set options for an output object

1.6 The server function

… connects inputs to outputs and (re-)calculates the outputs when changes in the inputs are detected (keyword: reactivity).

The server function takes two arguments

  • the input list of reactive input values
  • the output list of responding output objects

The names of the list elements must be identical to the input and output IDs in the user interface.

How to create elements in the output list

  1. Save objects to display to output$
  2. Build objects to display with render*()
  3. Access reactive values via input$
server <- function(input, output){
  output$mytext <- renderText({
    paste("You entered this number:", input$x)
  })
}

1.7 Rendering output

A render*() function

  • creates an object to be displayed by a corresponding*Output() function
  • takes as an argument an expression that creates the object (may be multiple lines of code included in {})
  • observes changes of reactive input values
renderPlot()      # Plot output         <-> plotOutput()
renderText()      # Text output         <-> textOutput() 
renderPrint()     # Printable output    <-> verbatimTextOutput()
renderTable()     # Table output        <-> tableOutput()
renderDataTable() # Data table output   <-> dataTableOutput() 
renderImage()     # Image file output   <-> imageOutput()
renderUI()        # UI (HTML) output    <-> uiOutput() 
downloadHandler() # File downloads      <-> downloadButton()

1.8 Exercise 1


Create an app with

  • a slider input n,
  • a text input custom_xlab and
  • a plot box.

The plot should be a boxplot showing n values drawn randomly from an uniform distribution between 0 and 1 (runif()).
The x-axis label of the boxplot (argument xlab) shall correspond to the text entered in custom_xlab.

Use the template found at the beginning of the script.

library(shiny)

# create web page
ui <- fluidPage(
  sliderInput(inputId = "n", label = "Sample size", 
              min = 1, max = 100, value = 50),
  textInput(inputId = "custom_xlab", label = "X-axis label"),
  plotOutput(outputId = "hist")
) 

# "server logic"; calculate output from user input
server <- function(input, output){
  output$hist <- renderPlot({
    x <- runif(input$n)
    boxplot(x, xlab = input$custom_xlab)
  })
} 

# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)

2 Customizing appearance

2.1 UI Layouts

UI layouts (to be used inside fluidPage())

2.1.1 Sidebar Layout

Image source: Mastering Shiny

sidebarLayout(       # automatic layout with
  sidebarPanel(...), # sidebar and
  mainPanel(...)     # main area
)

wellPanel() # color panel around inputs/outputs: used in the examples below

2.1.2 Custom layout

… using Shiny’s grid layout system (based on the Bootstrap 12-wide grid system)

Image source: Mastering Shiny

:

fluidRow(         # custom layout with
  column(6, ...), # two equal-width columns
  column(6, ...)  # width should add up to 12 
                  # within fluidRow() 
)

2.1.3 Tab layout

… using tabsetPanel()

tabsetPanel(
  tabPanel("NameTab1", ...), 
  tabPanel("NameTab2", ...), 
  tabPanel("NameTab3", ...)
)



Other layout types (e.g. with very many tabs navlistPanel() or with a bootstrap “navbar” navbarPage()) as well as an explanation of the grid system can be found at https://shiny.rstudio.com/articles/layout-guide.html

2.2 Custom HTML

The various Shiny functions for the user interface ultimately generate HTML code that is rendered by the browser. Therefore, it is possible to use HTML code directly to build the user interface without much effort.

  • Raw HTML can be used inside HTML("<div>...</div>")
  • or you can use the functions from the tags list to build a specific HTML tag, e.g.:
    • tags$div() to define a <div> element
    • tags$video() to add a video to the UI
  • As usual with HTML these tags can be nested into each other.

Example:

fluidPage(
  # Grid layout 
  fluidRow(
    # column
    column(12, # 12 = full width
      # Custom HTML using "tags"-functions
      tags$div(
        tags$p("This is the first paragraph"),
        tags$p("This is meaningless placeholder text but",
               tags$b("this"), 
               "word seems to be very important. ")
      ),
      
      # ... and some raw HTML
      
      HTML("<p>Here you see raw HTML, <strong>bold</strong>,
           <em>italic</em>, <u>underlined </u>and even formatted</p>
           <ul>
            <li>as</li>
            <li>a</li>
            <li>list.</li>
           </ul>
           
           <blockquote>
           <p>Quotes</p>
           </blockquote>
           
           <p>as well as <a href='https://qhelp.eu/'>links </a>are of 
           course also possible.</p>"),
      
      # Inputs: Slider
      sliderInput(inputId = "bins",
                  label = "Number of bins:",
                  min = 1,
                  max = 50,
                  value = 30)

    )
  )
)

This is the first paragraph

This is meaningless placeholder text but this word seems to be very important.

Here you see raw HTML, bold, italic, underlined and even formatted

  • as
  • a
  • list.

Quotes

as well as links are of course also possible.

See also:

  • Overview: https://shiny.rstudio.com/articles/html-tags.html
  • Shiny HTML Tags Glossary: https://shiny.rstudio.com/articles/tag-glossary.html

2.3 Mathematical expressions

The MathJax library (https://www.mathjax.org/) typesets Latex-style expressions on the fly

Example:

fluidPage(
  # load MathJax; only needs to be called once
  withMathJax(), 
  # displayed and inline equation
  "Here is an equation
   $$\\alpha + \\frac{1}{2},$$ 
   where \\(\\alpha = 2\\)."  
  
)
Here is an equation $$\alpha + \frac{1}{2},$$ where \(\alpha = 2\).

To dynamically adjust mathematical expressions, they can be generated in the server function with renderUI({withMathJax(...)}) and inserted into the UI using uiOutput(...). In this case it is necessary to call withMathJax(...) in each renderUI() function. See this example: https://shiny.rstudio.com/gallery/mathjax.html

2.4 Exercise 2


Select a (suitable) layout and customize the app that you programmed in exercise 1.
Give the app a title using titlePanel() (see ?titlePanel for help).

library(shiny)

# create web page
ui <- fluidPage(
  titlePanel("Boxplot of uniformly distributed values"),
  sidebarLayout(       # automatic layout with
    sidebarPanel(
      sliderInput(inputId = "n", label = "Sample size", 
                  min = 1, max = 100, value = 50),
      textInput(inputId = "custom_xlab", label = "X-axis label")
    ), # sidebar
    mainPanel(
      plotOutput(outputId = "hist")
    )     # main area
  )
) 

# "server logic"; calculate output from user input
server <- function(input, output){
  output$hist <- renderPlot({
    x <- runif(input$n)
    boxplot(x, xlab = input$custom_xlab)
  })
} 

# "connect" ui with server logic and create app
shinyApp(ui = ui, server = server)

3 Customizing reactions

3.1 Reactivity

Reactivity is a two-step process

  1. Reactive values notify objects that depend on them that they have changed (thus invalidating the objects).
  2. Objects created by reactive functions respond (e. g. by re-running the code that creates the objects).

In addition to the render*() functions there are other reactive constructs that allow for finer control over reactions.

reactive() # reactive expression
eventReactive() # delay reactions
observe() # reactive observer
observeEvent() # to run code on the server
reactiveValues() # object for storing reactive values
isolate() # non-reactive scope for an expression

3.2 Calculating values reactively

Try to find the problem in the following app:

ui <- fluidPage(
  fluidRow(
    column(7,
      sliderInput(inputId = "n", 
                  label   = "Number of samples", 
                  min = 1, max = 100, value = 10),
      verbatimTextOutput(outputId = "desc")
    ),
    column(5,
      plotOutput(outputId = "hist")
    )
  )
) 

server <- function(input, output){
  output$hist <- renderPlot( hist(rnorm(input$n), xlab = "x", main = "Histogram") )
  output$desc <- renderPrint( summary(rnorm(input$n)) )
}

Reactive expressions: reactive(), eventReactive()

  • Create an object to use in downstream code.
  • This object is called like a function, and it returns a value.
  • Whenever a reactive value changes, the dependent expressions are invalidated (and recalculated as a result).

The solution to the problem with the app above:

# hist() and summary() work on same data
# getdata() is called like a function and returns 
server <- function(input, output){
  getdata <- reactive( rnorm(input$n) )
  output$hist <- renderPlot( hist(getdata()) )
  output$desc <- renderPrint( summary(getdata()) )
}

eventReactive() reacts only to selected reactive values:

server <- function(input, output){
  getdata <- eventReactive(eventExpr = input$button, valueExpr = {
                           ... # Perform calculations only when input$button changes
                               # the reactive values in the valueExpr do not trigger invalidations.
  })
  ...
}

3.3 Performing actions reactively

Reactive observers: observe(), observeEvent()

  • Listen to a change of reactive values (often a button click).
  • Are called for their side effects.
  • Do not return values.
server <- function(input, output){
  ...
  
  observe({
    print(paste0("The value of input$x is now ", input$x))
  })
  
  observeEvent(input$savebutton, {
    write.table(getdata(), file = "foo.txt")
  })
}

3.4 reactive() vs observe()

reactive() / eventReactive() observe() / observeEvent()
Has to be called like a function Cannot be called
Returns a value / no side effects Side effects / no return value
Lazy (only executed if needed by other reactive object) Eager (executed as soon as used input reactives change)
Cached -
Typical use cases:
  • Create / manipulate / simulate data that will later be used by several reactive objects (e.g. renderPlot() and renderTable()).
Typical side effects:
  • save to file / upload to server
  • setting reactiveValues()
  • changing input elements: e.g. updateSliderInput()

3.5 Reactive Values

  • Notify dependent reactive constructs when the value changes (more precisely: when they are invalidated).
  • Can be read or overwritten within reactive context.
  • Use ... <- reactiveValues(...) inside the server function to create a list of named reactive values.
ui <- fluidPage(
      sliderInput(inputId = "n_sim", 
                  label   = "Number of samples", 
                  min = 1, max = 10, value = 5),
      actionButton(inputId = "sim_new", label = "Simulate data"),
      verbatimTextOutput(outputId = "df")
) 


server <- function(input, output){
  # create list of reactive values n and data
  r <- reactiveValues(n = 5, data = data.frame(id = 1:5, x = NA ))
  
  # simulate new data
  observeEvent(input$sim_new, {  
    r$n    <- input$n_sim        # overwrite rective values n and data
    r$data <- data.frame(id = 1:r$n, x = rnorm(r$n))
  })
  
  # show data frame
  output$df <- renderPrint(r$data)

}

There is also reactiveVal() to create a single reactive value. Note that reading and writing are a bit special:

server <- function(input, output){
  ...
  x <- reactiveVal(5) # create reactive value x
  
  observeEvent(input$button_add, {
    new_val <- x() + 1 # read reactive value x and add 1
    x(new_val)         # overwrite x with new_val
    
  })
  

}

3.6 Stop reactivity

  • isolate() can be used to prevent an object from being recalculated when the reactive variable is changed.

Example:

ui <- fluidPage(
      sliderInput(inputId = "n_sample", 
                  label   = "Number of samples", 
                  min = 1, max = 10, value = 5),
      actionButton(inputId = "draw_plot", label = "Draw plot"),
      plotOutput("ui_plot")
) 



server <- function(input, output, session){
  
 output$ui_plot <- renderPlot({
   input$draw_plot # Dependency on the button
   x <- rnorm(isolate(input$n_sample))
   hist(x)
 })
}

shinyApp(ui, server)

3.7 The reactive graph

To understand how a Shiny app reacts to changes in inputs, it is helpful to look at / construct the app’s reactive graph.

Create reactive graph automatically:

library(shiny)
options(shiny.reactlog = TRUE)    # enable reactlog

ui <- fluidPage(
  sliderInput("a", "Number a", 1, 10, 5),
  sliderInput("b", "Number b", 1, 10, 5),
  textInput("t", "Text t"),
  verbatimTextOutput("all_out"),
  verbatimTextOutput("a_out")
)

server <- function(input, output, session) {
  data <- reactive({ input$a * input$b})
  
  output$all_out <- renderPrint({
    paste("Text t:", input$t, " a * b =", data())
  })
  
  output$a_out <- renderPrint({
    paste("Number a:", input$a)
  })
  
}

shinyApp(ui, server) # start app
# Press Ctrl-F3 (Command-F3) to launch the reactive graph in a new tab.
# Refresh the page to see the recent activities.

In the beginning, it can be helpful to draw the reactive graph for various (simple) apps yourself.

4 Dynamic user interface

4.1 Update input controls

  • Each *Input()-function is paired with an update*()-function
  • E.g.: sliderInput() <-> updateSliderInput()

Example: Use an actionButton() to reset a slider.

ui <- fluidPage(
      sliderInput(inputId = "n", 
                  label   = "Number of samples", 
                  min = 1, max = 10, value = 5),
      actionButton(inputId = "reset_slider", label = "Reset slider"),
      verbatimTextOutput(outputId = "slider_out")
) 

# !!!
# We need to include a session object as an argument to our server 
# function so that shiny can "communicate" with the web page.
# !!!

server <- function(input, output, session){
  
  observeEvent(input$reset_slider, 
               updateSliderInput(session, "n", value = 5))

  output$slider_out <- renderPrint(input$n)
}

4.2 Conditional UI

conditionalPanel()

  • Only show part of UI if certain condition is met.
  • Condition must be specified as JavaScript code.
    • The user inputs can be accessed with input.InputID or input["InputID"].
    • For comparisons and logical operators in JavaScript see: https://www.w3schools.com/js/js_comparisons.asp

Example:

ui <- fluidPage(
      tags$p("Free drink with large pizzas (more than 20cm in diameter)."),
      sliderInput(inputId = "d_pizza", 
                  label   = "Diameter", 
                  min = 10, max = 40, value = 15),
      conditionalPanel(condition = "input.d_pizza > 20",
                       selectInput("drink",
                                   label = "Choose your free drink",
                                   choices = c("Water", "Beer", 
                                               "Coke", "Fanta"))),
      verbatimTextOutput("order")
      
      
) 


server <- function(input, output, session){
  
  output$order <- renderText({
                    drink <- ifelse(input$d_pizza > 20, 
                                    input$drink, 
                                    "None")
                    paste("Your order:\n- Pizza's diameter:", 
                          input$d_pizza,
                          "\n- Free drink:", drink)
    
    })
}

Free drink with large pizzas (more than 20cm in diameter.


4.3 Render reactive UI

  • Reactively creates a UI part from ui-functions (*Input(), *Output(), etc.) or HTML code.
  • More complex than conditionalPanel()…
    • … therefore also slower.
  • renderUI() in the server function is paired with uiOuput() in the ui object.
  • If several UI elements are to be created within renderUI, they must be combined in a tagList()
    • E.g. two sliders: tagList(sliderInput(...), sliderInput(...))

Example:

ui <- fluidPage(
      checkboxInput("ui_type", "I prefer sliders."),
      uiOutput("ui_numeric"),
      verbatimTextOutput("number")
) 

server <- function(input, output, session){
  
  output$ui_numeric <- renderUI({
                        if(input$ui_type){ # slider
                           sliderInput("num_chosen", 
                                       "Choose a number",
                                       0, 100, 0)
                        } else {           # numeric input
                           numericInput("num_chosen", 
                                        "Choose a number",
                                        0, 0, 100)  
                        }
  })

  output$number <- renderText(input$num_chosen)
}

shinyApp(ui, server)

4.4 Report progress

For time-consuming calculations, the user should be given feedback on how far the calculation has progressed (or at least that the app has not crashed).

  1. Loading animations: shinycssloaders::withSpinner() around *Output()
Loading...
library(shiny)
library(shinycssloaders)

## User interface with slider (input) and plot (output)
ui <- fluidPage(
  sliderInput(inputId = "n", 
              label   = "Sample size [millions]", 
              min = 20, max = 30, value = 20),
  withSpinner(plotOutput(outputId = "hist")) # add loading animation
) 

## Server function connecting input and output
server <- function(input, output){
  output$hist <- renderPlot({
    x <- rnorm(input$n * 1000000) # draw n random values
    hist(x)
  })
}

shinyApp(ui = ui, server = server)


  1. Progress indicators: withProgress() inside reactive(), observer() or render*()
library(shiny)
library(MASS)

set.seed(1606)

# simulate data
df1 <- as.data.frame(mvrnorm( n = 200, mu = c(0, 50), Sigma = rbind(c(1, 0.5), c(0.5, 1))))
names(df1) <- c("y", "x")

# User interface with slider (input) and plot (output)
ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "n", 
                  label   = "Number of bootstrap replicates", 
                  min = 100, max = 1000, value = 200, step =  10),
      actionButton("start_boot_sim", "Start")),
    mainPanel(
      plotOutput(outputId = "hist") # add loading animation
    )
  )) 

# Server function connecting input and output
server <- function(input, output){
  
  boot_rep <- eventReactive(input$start_boot_sim, {
    ###### add Progress bar
    withProgress(message = 'Bootstrap', value = 0, {
      n_sim <- input$n
      boot_sample <- numeric(n_sim)
      for(i in 1:n_sim){ 
        Sys.sleep(0.0005) # simulate a long computation
        boot_idx <- sample(1:nrow(df1), nrow(df1), replace = TRUE)
        
        boot_sample[i] <- cor(df1[boot_idx, "x"], df1[boot_idx, "y"])
        
        #### Increment progress bar in steps of n_sim/10
        if(i %% floor(n_sim/10) == 0) # modulo operator %% 
          incProgress(floor(n_sim/10)/n_sim, detail = paste("Bootstrap replicates ", i))
      }
      
    })
    boot_sample
  })
  
  output$hist <- renderPlot({
    ci <- round(quantile(boot_rep(), c(0.025, 0.975)), 3)
    br <- seq(ci[1], ci[2], length.out = 10)
    br <- c(br[1] - diff(br)[1] * 15:1, br, br[length(br)] + diff(br)[1] * 1:15)
    hist(boot_rep(), main = "Bootstrap distribution with 95%-CI", 
         xlab = "Correlation", xlim = c(0, 1), breaks = br,
         col = ifelse(br >= ci[1] & br < ci[2], "grey50", "grey90"))
    abline(v = ci)
  })
}

shinyApp(ui = ui, server = server)

4.5 Missing inputs

  • Reactive expressions or outputs can only procceed, if all inputs (input$...) exist.
  • E.g. if a user has not yet set an input and no default value has been defined, then an error message may occur.
  • To prevent this, the command req(innput$...) can be used. If no value is set for input$..., the calculation will be aborted without a warning message.
  • https://shiny.rstudio.com/articles/req.html

Example:

  output$hist_mi <- renderPlot({
    
    ## check requirements -> stops silently if input$distr is missing
    req(input$distr)
    
    x <- switch(input$disti,  # draw n random values
                Normal = rnorm(input$n_mi),
                Uniform = runif(input$n_mi))
    hist(x)
  })

4.6 Validate inputs

  • User input cannot be trusted! Therefore, methods are needed to validate the input.
  • validate(need(...)) tests a condition and returns a validation error if the test fails.In addition, the calculation is aborted as with req(...).
  • https://shiny.rstudio.com/articles/validation.html

Artificial Example: (Bad UI/UX!!!)

Solution (Still bad UI/UX!):

# Server function connecting input and output
  output$hist_mis <- renderPlot({
    
    ## Input validation
    validate(
        need(input$distr %in% c("normal", "uniform"), 
             "Only 'normal' and 'uniform' are supported distributions."),
        need(input$n > 0, 
             "The number of values drawn must be greater than 0.")
     )
    ##
    
    x <- switch(input$distr,  # draw n random values
                normal = rnorm(input$n),
                uniform = runif(input$n))
    hist(x)
  })

(In this case, however, one should consider the restrictions earlier: set the limits differently for sliderInput() and use selectInput() instead of textInput().)

4.7 Modal dialog

  • Modals are typically used for showing important messages.
  • Modals grab the user’s attention
  • … but they can also distract the user.
library(shiny)


# User interface 
ui <- fluidPage(
  actionButton("show_dialog", "Show modal dialog")
)

# Server function connecting input and output
server <- function(input, output){
  
  observeEvent(input$show_dialog, {
    showModal(
      modalDialog(
        title = "Important message",
        HTML("This is an exemplary modal dialog. <br>
      The dialogue can be terminated with cancel. <br>
      <b>Don't press OK</b> if you hate Google and if your IP address is super secret."),
      easyClose = TRUE,
      footer = tagList(
        modalButton("Cancel"),
        actionButton("ok", "OK")
      )
      ))
  })
  
  
  # css firework see: https://codepen.io/alvaromontoro/pen/MWrMEgW
  
  observeEvent(input$ok, {
    showModal(
      modalDialog(
        title = "HEHEHE",
        HTML('<iframe width="816" height="459" src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ?controls=0&autoplay=1" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>'),
        easyClose = TRUE,
        size = "l",
        footer = tagList(
          modalButton("Close")
        )
      ))
  })
  
}

shinyApp(ui = ui, server = server)

5 Shiny and R Markdown

You can see the result of an R Markdown document in front of you. This script was created with R Markdown, Shiny Apps and a little CSS and HTML magic. So you already have an impression of what is possible with R Markdown.

5.1 What is Markdown?

  • A so-called lightweight markup language
  • designed to be easy to write with any text editor
  • which is easy to read even in its raw form
  • created by John Gruber and Aaron Swartz in 2004

A Markdown-formatted document should be publishable as-is, as plain text, without looking like it’s been marked up with tags or formatting instructions. – John Gruber

There are many flavors of Markdown (GitHub Flavored Markdown, Markdown Extra)

R Markdown uses Pandoc’s Markdown an extended and slightly revised version of John Gruber’s Markdown syntax.

R Markdown supports many output formats including HTML, PDF, Beamer Presentations, HTML5 slides, books, dashboards, shiny applications, scientific articles…

5.2 Syntax (Pandoc’s Markdown)

R Markdown: The Definitive Guide by Yihui Xie, J. J. Allaire & Garrett Grolemund.

…. for more details about Pandoc’s Markdown visite: https://pandoc.org/MANUAL.html#pandocs-markdown.

5.3 Integrate R-Code and Shiny

R Markdown extends Markdown with R Code Chunks that can be executed and later displayed in the output format.

For example, it is possible to integrate the data analysis directly into a report and thus obtain a reproducible document (text + data analysis). In addition, Shiny Apps can also be integrated into the R Markdown document as long as the output format is HTML.

Download .Rmd example file -> Open it with RStudio -> Click Run Document

6 Other useful features

6.1 Automatic re-execution (trigger)

  • re-execute observer or reactive expression repeatedly: invalidateLater()
  • https://shiny.rstudio.com/reference/shiny/1.0.0/invalidatelater

6.2 Speed improvements & caching

Which part of the code takes the most time to complete?

  • Profile code to find bottlenecks: profvis package
  • https://shiny.rstudio.com/articles/profiling.html

Use caching to improve speed: bindCache()

  • cache all previous values vs. reactive(): only last value cached ,
  • app-level or session-level cache scope
  • persistent cache: shinyOptions(cache = cachem::cache_disk(“./cache_dir”))
  • https://shiny.rstudio.com/app-stories/weather-lookup-caching.html

6.3 reactiveConsole(TRUE)

  • “experimental feature that allows you to enable reactivity at the console”
  • https://shiny.rstudio.com/reference/shiny/1.7.0/reactiveconsole