Share shinyR: shiny in R

Shiny

Shiny is a package facilitating the creation of rich and interactive applications using basic and familiar R code. An impressive variety of looks and features are possible via Shiny. In addition, apps can be shared on a website after properly deployed. We’ll touch on how this all works below to help you begin making dashboard which actually matter.

Mechanics

Each Shiny app contains consists of a front-end that defines the visual appearance, along with a back-end which define rich responses to user interaction, or in other words the behavior of the app. ui() controls the front-end and sets up some many inputs and outputs that will be connected to the back-end, which is controlled by server(). Reactivity (primarily via reactive()) is a rich part of the back-end which helps simplify code. Let’s go through each of those three components:

  1. UI (user interface) - defines how app looks, feels, and behaves; controls layout and interactive components; three basic fundamental classes of functions for the UI:
    • input controls: let users interact with the app by selecting choices, e.g. text-boxes, drop downs, and sliders.
    • output controls: places the output on the page.
    • layout functions: Set up basic visual structure of the page.
  2. Server - defines how app works; renders and further defines outputs defined in the UI.
    • each render function is paired with a specific output function in the UI.
  3. Reactive programming - style of programming (declarative) where we outline in Shiny how we want things to work together, Shiny figures out when to execute.

At its simplest, a shiny app looks like this:

#ui and server
library(shiny)
ui <- fluidPage()
server <- function(input, output, session){}

#executes shiny app
shinyApp(ui, server)

A more realistic example of a Shiny app is given below. It summarizes and displays our choice of data set from the datasets package.

#ui with no layout, 1 input, 2 outputs
ui <- fluidPage(
  selectInput("dataset", label = "Dataset", choices = ls("package:datasets")),
  verbatimTextOutput("summary"),
  tableOutput("table")
)

#server with 2 outputs defined.
server <- function(input, output, session) {
  output$summary <- renderPrint({
    dataset <- get(input$dataset, "package:datasets")
    summary(dataset)
  })
  output$table <- renderTable({
    dataset <- get(input$dataset, "package:datasets")
    dataset
  })
}

#executes shiny call
shinyApp(ui, server)

Now let’s go through each of the three pieces to gain a much richer understanding of the possibilities afforded by Shiny.

UI

A user interface (UI or ui in R) captures the look and organization of the page. In addition they define the ways in which the user can interact with the features defined in the back-end. Inputs capture the different ways users can interact with the page, outputs set up components which will leverage inputs on the back-end, and layouts along with the order of inputs and outputs in the UI define how all of these elements are organized on the page. Let’s go into each of these components.

Inputs

All inputs have name as the first argument which is used to access it in the server under input$name. Most have label as the second argument to define a label readable to the user in the UI. Followed by these two arguments are the values for the inputs, which vary according to the input function. A good convention for naming inputs is as so:

ui_input("name", "Label for User", arg1 = 5, arg2 = 8)

As we’ll see in the outputs, the name we use to specify our inputs should be unique, because they populate an output list object we’ll use to connect the values controlled in the UI to the back-end server.

Input one or more values of different types

  • dateInput - single days.
  • dateRangeInput - one range of days.
  • sliderInput - single or range of numeric inputs.
  • numericInput - single numeric input.
  • selectInput - single or multiple categories; useful for limited choices.
  • radioButtons - limited choices.
  • checkboxGroupInput - single or multiple categories; useful for limited choices.
  • checkboxInput - single check box.
  • textInput - single line of text.
  • textAreaInput - larger area of text.

Perform more general operations

  • actionButton - pair with observeEvent or eventReactive to perform arbitrary operations.
  • actionLink - link or button whose value increments by one each time its pressed.
  • fileInput - upload a file.
  • passwordInput - input passwords (blocked out text).

Outputs

Outputs defined in the UI with name test are accessed in the server through output$test. Each output function on the front-end ui is coupled with an render function on the back-end server. Although there are a few more outputs than these, the three basic outputs are text, tables, and plots. And just like inputs, you’ll find that outputs are very customizable.

Core output functions

  • textOutput + renderText - outputs regular text to the UI.
  • verbatimTextOutput + renderPrint - translates code to print out console output.
  • tableOutput + renderTable - renders a static table displaying all of the data; useful for small tables.
  • DT::DTOutput + DT::renderDT - renders a dynamic table, showing fixed number of rows; very flexible and useful for large tables.
  • plotOutput + renderPlot - renders plots; incredibly interactive in that plots can perform arbitrary actions through events, i.e. click, dblClick, hover, and brush.

Additional output functions

  • imageOutput + renderImage - renders images onto the UI.
  • uiOutput + htmlOutput + renderUI - renders reactive HTML.

We’ll come back to these on the server side to give more detail.

Layouts

Provides high-level visual structure of app. Created by a hierarchy of function calls which can easily be skimmed to visualize the app.

  • fluidPage - sets up all the HTML, CSS, and JS that Shiny needs using a layout system called Bootstrap which has attractive defaults, and must be customized to reach greater control of app’s visual appearance.
  • navbarPage + tabPanel - creates a page containing a top-level navigation bar to toggle through different pages.
  • sidebarLayout + mainPanel + sidebarPanel - used in conjunction to achieve the page with a one sidebar panel like so: sidebarLayout(sidebarPanel(#inputs), mainPanel(#outputs)); can switch the two to swap the position of the sidebar.
  • fluidRow + column - used to achieve multiple rows of inputs with varying number and lengths of panels, e.g. fluidRow(column(4, ...), column(8, ...)) or fluidRow(column(4, ...), column(4, ...), column(4, ...)).

Advanced UI

inputs, outputs and layouts give us attractive presets that we can play around with to define the look and feel of the app. But if we want even more control, it is completely possible to incorporate web programming into Shiny to make a really advanced UI. A large number of resources exist for web programming, so it would be silly to not use them if they are at your disposal.

Another topic we won’t touch on is creating a dynamic UI. What this means is that user input can dynamically change the user interface, which is otherwise static.

Server

Unlike ui which consists of inputs within layout functions, server is defined as a function with three arguments: input, output, and session.

The input argument is a list-like object containing all input data from the browser, named by the input ID as we mentioned above. For example, if our UI contained a numeric input named count, that is:

ui <- fluidPage(
  numericInput("count", label = "Number of values", value = 100)
)

then we could access this value with input$count. Initially it will contain the default value of 100 and will update according to the user’s input. Two other rules surrounding input objects are that they are read-only and can only be updated through functions like updateNumericInput, and that they can only be read within a reactive context, i.e. in a render or reactive function.

output objects are identical to input except that they are used to send output instead of receive input. They are always used in tandem with a render function as so:

ui <- fluidPage(
  textOutput("greeting")
)
server <- function(input, output, session) {
  output$greeting <- renderText("Hello human!")
}

Some of the magic of Shiny for translating output is contained in the render function which:

  • sets up a special reactive context to automatically track what inputs the output uses.
  • converts the output of your R code into HTML suitable for display on a web page.

Apart from that there isn’t anything major that a good coding style won’t allow us to achieve on the server side of these apps.

Advanced Server

Uploads and downloads while not conceptually advanced, do require a little extra work on the server side. On the UI side their treatment is straight-forward:

  • fileInput - allows user to input a file.
  • downloadButton + downloadLink - allows user to download a file.

Consider the file input example below, with special attention on req to wait until first file is uploaded, and for the accept argument to limit possible inputs:

ui <- fluidPage(
  fileInput("file", NULL, accept = c(".csv", ".tsv")),
  numericInput("n", "Rows", value = 5, min = 1, step = 1),
  tableOutput("head")
)
server <- function(input, output, session) {
  data <- reactive({
    req(input$file)
    
    ext <- tools::file_ext(input$file$name)
    switch(ext,
      csv = vroom::vroom(input$file$datapath, delim = ","),
      tsv = vroom::vroom(input$file$datapath, delim = "\t"),
      validate("Invalid file; Please upload a .csv or .tsv file")
    )
  })
  output$head <- renderTable({
    head(data(), input$n)
  })
}

Setting up a download relies on downloadHandler on the server side in a manner similar to this:

output$download <- downloadHandler(
  filename = function() {
    paste0(input$dataset, ".csv")
  },
  content = function(file) {
    write.csv(data(), file)
  }
)

More details on downloads are given at the source.

Tidy evaluation is also a topic that should stimulate some caution around leveraging tidyverse code within a Shiny app, specifically a server. This topic is relatively advanced so please refer to the source.

Reactive Programming

Reactive programming is the secret to Shiny’s reactivity. I’ll defer talking about reactive programming to Mastering Shiny, but I will touch on reactive expressions. Reactive expressions are the equivalent of functions for a Shiny app. They are also like inputs in that we can use their results in an output, and like outputs in that they depends on inputs and know when to update. Like functions, reactive expressions are important because they:

  • increase the efficiency of the app by enabling less recomputation.
  • make it easier to understand and simplify the implicit reactive graph in our app.

For a very simplistic example, suppose our server look like so:

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste0("Hello ", input$name, "!")
  })
}

We could rewrite it using a reactive expression like this:

server <- function(input, output, session) {
  text <- reactive(paste0("Hello ", input$name, "!"))
  output$greeting <- renderText(text())
}

In a situation where we were repeatedly pasting strings together, something like this would be useful. Just like in regular programming, the key to writing good code is simplifying, and reactive expressions (like functions) are what make this happen. The only added complexity is that if we want to improve the efficiency of the code, we also have to take into account the implicit reactive graph present in our app.

Apart from reactive, there are other functions which help our app react to user input, primarily focused on the timing of when the code is evaluated.

reactiveTimer - used to make a reactive expression invalidate (i.e. refresh) itself more often than it otherwise would; its hidden dependency is the current time.
eventReactive - paired with actionButton allows one to evaluate a piece of code on click, i.e.

ui <- fluidPage(      
  actionButton("simulate", "Simulate!")
)

server <- function(input, output, session) {
  x1 <- reactive({
    input$simulate
    rpois(input$n, input$lambda1)
  })
}

observeEvent - similar to eventReactive, except that it evaluates each time its dependency changes.

For example, the below piece of code prints a message to the console each time that name is updated:

server <- function(input, output, session) {
  text <- reactive(paste0("Hello ", input$name, "!"))
  
  output$greeting <- renderText(text())
  observeEvent(input$name, {
    message("Greeting performed")
  })
}

Reactive programming is a very subtle way to programming and is the key to tapping into Shiny’s interactivity. I highly recommend Mastering Shiny as a resource to dig deeper into this.

Assorted Features

In this next section I touch on some of the features I thought would interest someone new to Shiny, and which I feel have high ROI. Deployment is crucial to someone trying to figure out how to share their app either publicly online or within an organization. Interactive plots really ratchet up the interactivity of Shiny, and is a well-developed feature unique to plotOutput. Bookmarking and user feedback are understated features which can enhance collaboration and the user experience.

Deployment

When its time to share your Shiny app with the world, you have three options outline here:

  • Shinyapps.io - leverage R’s cloud services to deploy under a number of options; this option is probably the easiest and is free for up to 5 applications and 25 active hours.
  • Personal Server - configure your server using Shiny server open source software provided for free by R; seems like the most intensive option with little support; may be worth it for those who already have a server set up.
  • RStudio Connect - leverage R’s cloud services to deploy with a number of valuable features such as pro drivers.

Note that when deployed, apps tend to behave a little differently and may reveal new errors. Be prepared for this and revise the app after deployment as needed.

Interactive Plots

plotOutput() is not only an output, but an input that responds to four different mouse events: click, dblClick, hover, and brush. To enable this feature for a given plot, we specify the argument corresponding the listed mouse event, e.g. plotOutput("plot", click = "plot_click"). This triggers Shiny to create input$plot_click which we can use leverage mouse clicks. The code below creates a simple example which returns the value of a point upon clicking.

ui <- basicPage(
  plotOutput("plot", click = "plot_click"),
  verbatimTextOutput("info")
)

server <- function(input, output) {
  output$plot <- renderPlot({
    plot(mtcars$wt, mtcars$mpg)
  }, res = 96)

  output$info <- renderPrint({
    req(input$plot_click) #prevents action before 1st click
    x <- round(input$plot_click$x, 2)
    y <- round(input$plot_click$y, 2)
    cat("[", x, ", ", y, "]", sep = "")
  })
}

Often though, there’s little need to use the exact information coming from input$click. Instead, its preferred to use the nearPoints helper for showing points near the event:

ui <- fluidPage(
  plotOutput("plot", click = clickOpts("click")),
  tableOutput("data")
)
server <- function(input, output, session) {
  output$plot <- renderPlot({
    plot(mtcars$wt, mtcars$mpg)
  }, res = 96)
  
  output$data <- renderTable({
    nearPoints(mtcars, input$click, xvar = "wt", yvar = "mpg")
  })
}

Fortunately, click, dblClick, and hover work very similarly, so there’s little need to change anything else when implementing these besides the name of the argument. Additionally, clickOpts, dblclickOpts and hoverOpts provide additional control over these events. One last point is that it is perfectly fine to use multiple interaction types inside one plot. brush is a little more difficult, so again we defer to the source.

One very last point is that for easy interactivity with certain plots, we can use plotlyOutput and general plotly tools to embed their interactive graphs within a Shiny application. There are many nice defaults and options, so this is a nice start if we don’t require anything too custom.

Bookmarking

Unlike most internet apps, Shiny does not capture the current state of the app by default. This means that you can’t bookmark its current state and return to it in the future, which hinders collaboration and continued exploration of the app. One way to get around this is by using bookmarking. One option is to:

  1. Add bookmarkButton() to the UI - generates a button the user clicks to generate a bookmarked url.
  2. Turn ui into a function - needed to allow Shiny to modify the default values of each input.
  3. Add enableBoookmarking = "url" to the shinyApp() call - allows bookmarking by the url.

Together all of this can look like this:

ui <- function(request) {
  fluidPage(
    sidebarLayout(
      sidebarPanel(
        sliderInput("omega", "omega", value = 1, min = -2, max = 2, step = 0.01),
        sliderInput("delta", "delta", value = 1, min = 0, max = 2, step = 0.01),
        sliderInput("damping", "damping", value = 1, min = 0.9, max = 1, step = 0.001),
        numericInput("length", "length", value = 100),
        bookmarkButton()
      ),
      mainPanel(
        plotOutput("fig")
      )))
}
#server
shinyApp(ui, server, enableBookmarking = "url")

With results in URLs which look something like this:

  • http://127.0.0.1:4087/?_inputs_&damping=1&delta=1&length=100&omega=1
  • http://127.0.0.1:4087/?_inputs_&damping=0.966&delta=1.25&length=100&omega=-0.54

Alternatively, instead of using an explicit button we could automatically update the url each time the inputs change by adding this to our server:

server <- function(input, output, session){
  #arbitrary server stuff
  
  observe({
    reactiveValuesToList(input)
    session$doBookmark()
  })
  onBookmarked(updateQueryString)
}
shinyApp(ui, server, enableBookmarking = "url")

This way we could just take our url at any point and use it to return to the app or send it to someone else. But as you can imagine, an app with many inputs would result in a very long url. So apart from these two ways using enableBookmarking = "url", we could use enableBookmarking = "server" to save the state of the app to a .rds file on the server.

In this case, we end our app with:

shinyApp(ui, server, enableBookmarking = "server")

and see that the bookmark button (which we would need for this way) would result in urls like:

  • http://127.0.0.1:4087/?_state_id_=0d645f1b28f05c97
  • http://127.0.0.1:4087/?_state_id_=87b56383d8a1062c

which are paired with matching directories like:

  • shiny_bookmarks/0d645f1b28f05c971
  • shiny_bookmarks/87b56383d8a1062c

One drawback is that its not obvious how long these files need to hang around for, which makes it tricky to know what to delete over time to free up space. One other common scenario is if the app has tabs. In this case supply an id in the call to tabsetPanel to restore the active tab. A couple of more scenarios are handled at the source.

User Feedback

User feedback gives the user insight that something is happening behind the scenes, which is a great comfort. Some uses for user feedback are to:

  • better messages when inputs are invalid or an app errors.
  • progress updates for operations which take some time.
  • confirmation dialog to give peace of mind; ability to undo an action.

Through either the shinyFeedback package or the waiter package, we can achieve these aims. But given the discussion. on this is actually quite extensive so again, defer.

Random Tips and Tricks

  • CMD/CTRL + Shift + Enter makes your app run. Console will not work until we close the app. A shortcut for that is Esc.
  • If a certain feature is not working of your app is not working like uploads and downloads, open it in your app and try there.
  • Modularizing code is a good way to simplify a very complex app, and really any code in general.
  • All input, output, and layout functions return HTML. We can access the HTML code by printing it to the console, and learn more about the code behind webpages here.

Conclusion

Shiny is yet another way that R is evolving past a statistical programming language into an avenue all its own. Considering that webpages are part HTML (for content), part CSS (for styling), and part JavaScript (for behavior), its really remarkable that what was once was a statistical programming language can now translate simple functions into applications that can live online.

Now more than ever its becoming easier for a data scientist to share their insights in engaging ways. To see just how far people are taking this, please visit the Shiny Contest to browse through apps you can aspire to.

comments powered by Disqus