Chapter 14 Dashboards

Since preparing the data and analyzing them is only a part of the story, we tackled in Section 6 how to generate a report from your analysis (and briefly how to analyze your data within your report) while Section 11 discussed points to consider to be as impactful in your communication as possible. For the latter, many formats on how to deliver and present your results were suggested, including the use of interactivity through dashboards. Since it is possible to build dashboard in R, we had to include a section that would introduce you to such solution. So embrace it, integrate it to your toolbox, and soon it will be your turn to shine during presentations!

14.1 Objectives

We have certainly been all through situations in which we spent a lot of time analyzing data for our study, built our report and our story, spent time in perfecting our presentation. But when come the day of the presentation to your manager and colleagues, we get questions such as: What would happen if we split the results between users/non users, or between gender for instance? In case you haven’t been prepared for this question, and didn’t run the analysis up-front, you probably answered something like: Let me re-run some analyses and I’ll update you on that!

Now imagine that you are in a similar situation, except that when such question arises, you have a tool that can answer live their questions using the data and the same analysis. Wouldn’t that bring your discussion to another level? Even better: Imagine you can share this tool with your colleagues for them to use it and answer their own questions, or use it for their own projects, even if they are not good at coding (nor even slightly familiar with R)?

Simply said, this is one of the roles of dashboards, as it brings interactivity to results (tables, graphs) by re-running and updating them when data, options, and/or parameters are being altered.

The goal of this section is to build such dashboard using R and the {shiny} package.

14.2 Introduction to Shiny through an Example

14.2.1 What is a Shiny application?

In case you have already been through the visualization section 5, you’ve already been briefly introduced to some sort of dashboard in R through the {esquisse} package. In this section, the {shiny} package is used to build such dashboard.

{shiny} is an R package that allows you to directly create from R interactive web applications. Its goal is to convert your R code into an application that can be accessible and used by anyone through their web browser, without having to be familiar with R.

This procedure is made available as {shiny} uses some carefully curated set of user interface (UI) functions that generate the HTML, CSS, and JavaScript code needed for most common tasks. In most cases, further knowledge of these languages is not required…unless you want to push your application further. Additionally, it introduces a new way of programming called reactive programming, which tracks automatically dependencies between code: When a change in an input is detected, any code that is affected by this change will automatically be updated.

14.2.2 Starting with Shiny

To create your very first shiny application, you can click on R studio in the new page icon and select Shiny Web App… Once you filled in the relevant information (name, author), you can then decide whether you want to create one unique file (app.R) or multiple files (ui.R and server.R).

Both solutions are equivalent and work the same: In both cases, a ui() and a server() function are generated. Due to better readability, and to ease its maintenance over time, we recommend to use the single file for short applications, and to use multiple files for larger applications (larger meaning with more code lines).

For our short application, it seems more convenient to use a single file.

14.2.3 Illustration

For illustration, let’s consider a simple application in which we would import a data set that contains sensory data, as collected from a trained panel. In this example, the data set structure follows the one in biscuits_sensory_profile.xlsx.

For simplification purposes, the code developed for this application requires that the data set contains one column called Judge (containing the panelist information), one column called Product (containing the product information), all the other columns being quantitative (corresponding to the sensory attributes).

The goal of the application is then to compute from this raw data set the sensory profiles of the products and to display them on screen. Furthermore, we also represent these sensory profiles graphically in a spider plot or in a circular bar plot. Since the main goal of shiny application is in its interactivity, the user should have the opportunity to remove/add/reorder the attributes on the table and plots, and to hide/show products to increase visibility.

Once the graphics match our needs, we also propose to download it as a .png file to integrate it in our report.

From a learning perspective, this application introduces you specifically to:

  • Importing an external file to the application;
  • Create options that that are both independent (type of graph to produce) and depend of the file imported (list of attributes and products);
  • Run some analyses (compute the means) and display the results as a table and as a plot;
  • Export the graph to your computer as a png file.

The code presented in the next section can be found in app.R. In the next sections, pieces of code are shown for explanation, and should/can not be run on their own. Instead, the entire application should be run.

14.2.3.1 User Interface

The user interface (UI) is the part of the application that controls what the user sees and controls. In our example, the UI is separated into two parts:

  • The left panel contains the options that the user can manually change;
  • The right (or main) panel contains the outputs.

In the app.R file, this information is stored in the ui() function, and the two panels can be found in sidebarPanel() and in mainPanel() respectively.

In sidebarPanel(), all the options are set up. These options include fileInput() for importing the data set, or radioButtons() to control the type of plot to generate. A large list of options exist including numericInput(), sliderInput(), textInput(), passwordInput(), dateInput(), selectInput(), checkboxInput() etc. Note that this library of options can be extended by adding checkboxGroupInput() from the {shinyjs} package56.

For most of these options, setting them up is quite straightforward, especially when (say) the range of values is already known beforehand (e.g. p-value ranging from 0 to 1, with default value at 0.05). However, in some cases, the option of interest cannot be defined on the UI side since they depend on the data itself (e.g. the product or attribute selection in our example). In such situation, these options are created on the server side, and are retrieved on the UI side through uiOutput().

On mainPanel(), tabsetPanel() and tabPanel() control for the design of the output section. In our example, two tabs (one for the table and one for the graph) are created, although they could have been printed together on one page.

In our simple example, the mainPanel() is only use to export results computed on the server side. Depending on the type of output generated, the correct function used to retrieve the results should be used:

  • For tables, tableOutput() is used to retrieve the table generated with renderTable();
  • For graphics, plotOutput() is used to retrieve the plot generated with renderPlot();
  • For elements to download, downloadButton() is used to retrieve the element (here a plot, but could be an Excel or PowerPoint file for instance) generated with downloadHandler().

Note the pattern in the namings of the complementary functions: The xxxOutput() function (UI side) is used to retrieve the output generated (server side) by the corresponding renderXxx() function. This also applies with uiOutput() and renderUI().

14.2.3.2 Server

The server side of the application is where all the computations are being performed, including the construction of tables, figures, etc.

Since the options defined on the UI side should affect the computations performed (e.g. our decision on the type of plot to design should affect the plot generated), we need to communicate these decisions to the server, and use them. On the server side, any information (or option) passed to the server side is done through input$name_option. In our previous example regarding the type of graph to generate, this is shown as input$plottype, as defined by:

radioButtons("plottype", "Type of Plot to Draw:", 
             choices=c("Spider Plot"="line", "Circular Barplot"="bar"), 
             selected="line", inline=TRUE)

In this case, if the user select Spider Plot (resp. Circular Barplot), input$plottype will take the value line (resp. bar).

Reversely, any information that is being build on the server side and that should be passed on the UI part of the application can either be done via the xxxOutput()/renderXxx() combination presented before (useful for showing results), including the renderUI()/uiOutput() combination (useful for options that are server-dependent).

Following a similar communication system than the one from UI to server, the part generated on the server side is stored as output$name_option (defined as renderUI()) and is captured on the UI side using uiOutput("name_option").

In our example, the latter combination is used for the two options that require reading the data set first, namely the selection of attributes and the selection of products.

# server side:
  output$attribute <- renderUI({
    
    req(mydata())
    
    items <- mydata() %>% 
      pull(Attribute) %>% 
      as.character() %>% 
      unique()
    
    selectInput("attribute", "Select the Attributes (in order) ", choices=items, selected=items, multiple=TRUE)
    
  })

  output$product <- renderUI({
    
    req(mydata())
    items <- mydata() %>% 
      pull(Product) %>% 
      unique()
    
    checkboxGroupInput("product", "Select the Products to Display:", choices=items, selected=items)
    
  })
  
# UI side:
  uiOutput("attribute")
  uiOutput("product")

Lastly, we have elements that are only relevant on the server side, namely the computation themselves. In our example, these are results of a function called reactive().

Reactivity (and its corresponding reactive() function) is a great lazy feature of {shiny} that was designed so that the computations are only performed when necessary, i.e. when changes in an input affects computations. This laziness is of great power since only computations that are strictly needed are being performed, hence increasing speed by limiting the computation power required to its minimum.

Let’s break this down in a simple example:

  mydata <- reactive({
    
    req(input$datafile)
    data <- readxl::read_xlsx(input$datafile$datapath, sheet=1) %>% 
      pivot_longer(-c(Judge, Product), names_to="Attribute", values_to="Score") %>% 
      mutate(Attribute = fct_inorder(Attribute), Score = as.numeric(Score)) %>% 
      group_by(Product, Attribute) %>% 
      summarize(Score = mean(Score)) %>% 
      ungroup()
    
    return(data)
    
  })

In this section, the file selected by the user is read through the fileInput() option called datafile on the UI side. Note that in this case, the path of the file is stored in the object called datapath, meaning that to access this file, we need to read input$datafile$datapath.

Once read (here using {readxl}), some small transformations to the data are performed before saving its final version in an object called mydata. Since this block of code only depends on input$datafile (UI side), this part is no longer used unless datafile is being updated or changed.

For the computation of the means, the same procedure applies as well:

  mymean <- reactive({
    
    req(mydata(), input$attribute, input$product)
    mymean <- mydata() %>% 
      mutate(across(c("Product", "Attribute"), as.character)) %>%
      filter(Attribute %in% input$attribute) %>% 
      mutate(Product = factor(Product, input$product),
             Attribute = factor(Attribute, input$attribute),
             Score = format(round(Score, 2), nsmall=2)) %>% 
      pivot_wider(names_from=Product, values_from=Score)
    
    return(mymean)
  })

For this reactive() block, mymean depends on mydata, input$attribute, and input$product. This means that if datafile (read mydata), input$attribute and/or input$product change, the computations re-run and mymean is getting updated.

For small and simple examples like ours, this domain of reactivity may be sufficient, and would be sufficient in many cases. There are however some few points that require a bit more explanations.

First, we advise that you use reactive() as much as possible: In our example, we could have created the code to build the graph within renderPlot(). However, this way of coding is not efficient since it would always be updated, even when it is not necessary. For small examples such as the one proposed here, this may not make much difference, but for larger applications it would have a larger impact. This is why we prefer to create the graphs in a reactive() instance, and simply retrieve it for display.

Second, and as you may have seen already, the output of a reactive() section can be re-used in other sections. This means that just like in regular coding, you can save elements in R object that you can re-use later (e.g. mydata, mymean, or myplot). However, these elements act like functions, meaning that if you want to call them, you should do it as mydata() for instance. More generally, let’s imagine that mydata is a list with two elements (say mydata$element1 and mydata$element2), we would retrieve element1 as mydata()$element1.

Third, let’s introduce the function req() that is used at the start of almost every block of code on the server side. To do so, let’s take the example of output$attribute which starts with req(mydata()). The req() functions aims in requiring the object mentioned (here mydata()) before running: if mydata() doesn’t exist, then output$attribute is set as NULL. This small line of code comes handy as it avoids returning design errors: How to extract a list of attributes from data that do not exist yet?

Finally, the application that we are developing here is over-reactive as every change we do will create update results. To highlight this, just remove some attributes from the list and you’ll see the mean table or graphic being updated. In our small example, this is not too problematic since the application runs fast, but in other instances in which more computation is required, you may not want to wait that each little change done is being processed. To over come this, you can replace reactive() by eventReactive() combined with a button (e.g. Run or Apply changes) that only trigger changes once pressed. This means that changes are only performed on a user action.

14.2.4 Deploying the Application

To run the application, three options exist (within RStudio):

  • Push the Run app button on the task bar of your script (a menu allows you to run the app in the Viewer window, or as a new window).
  • Type directly shiny::runApp('code') in R.
  • Use the shortcut CTRL+SHIFT+ENTER (on windows).

In this case, your computer (and RStudio) will use a virtual local server to build the application.

Unfortunately, this solution is not sufficient in case you really want to share it with colleagues. In such case, you need to publish your app by installing it on a server. Ideally, you have access to a server that can host such application (check with your IT department). If not, you can always create an account on https://www.shinyapps.io/admin/ and install it there. Note that there is a free registration option, which comes with limitations (number of applications to install). Also, before using their services, make sure that their conditions (privacy, protection of the data, etc.) suit you and your company policy.

14.3 To go further…

Quickly, {shiny} became popular and many researchers developed additional packages and tools to further enhance it. Without being exhaustive, here are a few tools, widgets, and packages that we often use as they provide interesting additional features. But don’t hesitate to look online for features that answer your needs!

14.3.1 Personalizing and Tuning your application

If you start building many applications in {shiny}, you might get tired of its default layout. Fortunately, the overall appearance of applications can be modified easily, especially if you have notion in other programming language such as CSS and HTML. If not, no worries, there are alternative solutions for you, including the {bslib} package. To change the layout, simply load the library, and add the following piece of code at the start of your application (here for using the theme darkly):

fluidPage(
  theme = bslib::bs_theme(bootswatch = "darkly")
)

{bslib} also allows you creating your own theme that match your company style. So do not hesitate to take some time to build it once, and to apply it to all your applications.

Besides changing the overall theme of your applications, there are certain details that can make your overall application more appealing and easier to use. In our short example, you may have noticed that each option and each output is a separate line. This is sufficient here since the number of options and outputs are very limited. However, as the application grows in size, this solution is no longer sustainable as you do not want the users to scroll down a long way to adjust all the options.

Instead, you can combine different options (or outputs) on one line. To do so, {shiny} works by defining each panel as a table with as many rows as needed and 12 columns. When not specified, the whole width (i.e. the 12 columns) of the screen is used, but this could be controlled through the column() function.

In our example, we could have position the product selection and the attribute selection on the same line using:

fixedRow(
  column(6, uiOutput("product")),
  column(6, uiOutput("attribute"))
)

14.3.2 Upgrading Tables

The default table built in {shiny} using the tableOutput()/renderTable() combination is very handy, yet limited in its layout. Fortunately, many additional packages that create HTML tables have been developed to provide alternative solution to build more flexible tables.

In particular, we recommend the use of the {DT} and {rhandsontable} packages as they are very simple to use, and yet provides a large variety of powerful design options. Just to name a few, it allows:

  • cells or text formatting (e.g. coloring, rounding, adding currencies or other units, etc.);
  • merge rows or columns;
  • add search/filter fields to columns of the table;
  • provide interactivity, that for instance can be connected to graphics;
  • include graphics within cells;
  • allows manual editing, giving the user the chance to fill in or modify some cells;

To build such table, you will need to load the corresponding packages. For {DT} tables, you can generate and retrieve them using the complementary functions renderDataTable() and dataTableOutput(), or its concise forms renderDT() and DTOutput()57. For {rhandontable} tables, you can generate and retrieve them using renderRHandsontable() and rHandsontableOutput().

For more information and examples, please look at https://rstudio.github.io/DT/ and https://jrowen.github.io/rhandsontable/.

14.3.3 Building Dashboard

The example used here illustrates the power of {shiny}. However, it is limited to our own data set, meaning that it is study specific. What if we would want to create a dashboard, that is connected to a database for instance and that updates its results as soon as new data is being collected?

This is of course the next step, and {shiny} can handle this thanks to the {shinydashboard} package.

In its principle, {shinydashboard} works in a similar way to {shiny} itself, except for the structure of the UI. For {shinydashboard}, the UI contains three sections as shown below (the example below generates an empty dashboard.):

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody()
)

server <- function(input, output) { }

shinyApp(ui, server)

It is then your task to fill in the dashboard by for instance connecting to your data source (e.g. a simple data set, a database, etc.) and to fill in your different tabs with the interactivity and outputs of interest.

For more information, including a comprehensive Get started section, please visit https://rstudio.github.io/shinydashboard/

14.3.4 Interactive Graphics

Through its way of working, {shiny} creates some interactivity to graphics by updating it when changing some options. This is hence done by replacing a static graph by another static graph.

However, R provides other options that creates interactive graphs directly. This can be done thanks to the {plotly} library.

{plotly] is an alternative library to {ggplot2} that can be used to build R graphics within the R environment: It is not specific to {shiny} and can be used outside Shiny applications. To build {plotly} visualizations, you can build it directly from scratch using the plot_ly() function. It is out of the scope of this book to develop further how {plotly} works, mainly because we made the decision to explain in details how {ggplot2} works. And fortunately, {plotly} provides an easy solution to convert automatically {ggplot2} graph to {plotly} thanks to the ggplotly() function.

For more information, please visit https://plotly.com/r/

14.3.5 Interactive Documents

Ultimately, {shiny} can also be combined to other tools such as {rmarkdown} to build interactive tutorials, teaching material, etc. This is done by integrating the interactivity of {shiny} to propose options and reactive outputs into a text editor through {rmarkdown}.

To integrate {shiny} in your {rmarkdown}, simply add runtime: shiny in the YAML metadata of your R markdown document.

14.3.6 Documentation and Books

Thanks to its way of working and its numerous extensions, there is (almost) no limit to applications you can build (except maybe your imagination?). For inspiration, and to get a better idea of the powerful applications that you can build, have a look at the gallery on the official shiny webpage: https://shiny.rstudio.com/gallery/

In this section, we just introduced you to the main functions available in {shiny}, but if you want to go further, there is a whole world for you to explore. Of course, a lot of material is available online, and we would not know where to start to guide you. However, we strongly recommend you to start with the book from Hadley Wickham entitled Mastering Shiny (REF) as it is comprehensive and will give you the kick start that you need…and more.


  1. To use {shinyjs}, you need to load the library and add useShinyjs(), at the start of your ui() code.↩︎

  2. the table should be generated using datatable().↩︎