class: center, middle # A Brief Introduction to R-Shiny and Dashboards ### Ryan Hafen & J. Hathaway ??? --- class: middle, center # Agenda .right-column[ .left[ 1. ### Introduction to R-Shiny and Dashboards 3. ### Making our first R-Shiny dashboard 4. ### A few rules for dashboards 5. ### How do I learn more? ] ] .left-column[ ![:scale 50%](img/shiny.png) ] --- class: center, middle, inverse # Introduction to R-Shiny and Dashboards --- class: center, middle # :Dashboards: ## Opinionated views for decision making and insight ![:scale 45%](img/car-dashboard.jpg) ![:scale 45%](img/dashboard_dashboard.png) ??? Focus on dashboards because this is usually the type of thing Shiny is most useful for. A dashboard typically displays summaries or subsets of data in a targeted way that provides users with understanding and insight. Think of a car dashboard - it shows you a lot of the important information you need in a clear and concise way. The designers make careful decisions about what sits in front of the drivers view. The dashboard metaphor is a bit complicated as the driver of the vehicle can hear and feel much more than what is before their eyes. While a data dashboard only provides a visual sensation of the data. Your charts, tables, and summaries are all that the user sees of data behind the scenes. --- class: center, middle # What is R-Shiny? .left-column[ .left[ ### An R package that makes it easy to build interactive web apps straight from the R language. ### [RStudio Shiny website](https://shiny.rstudio.com/) ] ] .right-column[ ![:scale 75%](img/rstudio.png) ] ??? Due to the short time that we have, this will be a very fast overview and introduction to Shiny. We won't be able to go into a lot of detail but will point to many resources along the way. --- class: center, middle # What can I build with Shiny? ![:scale 65%](img/gallery.png) https://shiny.rstudio.com/gallery/ ??? There are many examples out there but I would recommend looking at this gallery for examples of what you can do and for inspiration. --- # Why use R Shiny? - ### Great for fast prototyping - ### Only need to know R (usually*) - ### Easy to make use of R's stats/modeling/visualization packages - ### Good for dashboards or very simple apps used internally at your organization - ### More customizable than many BI tools ??? As soon as you want to change the look of your site or add more custom or dynamic interactions, it becomes useful to know some HTML, CSS, JavaScript. --- # Why not use R Shiny? - ### For non-trivial apps the codebase can become very complex and hard to follow - ### Difficult to transition a shiny app to a support team, even if they know R/Shiny - ### Deployment requires a special server setup and hosting can be complicated or expensive - ### In most cases, not suitable for production environments - ### R is not a natural environment for specifying UI/UX - ### Performance: UI interactions requiring round trips to R that could easily be handled in the web browser ??? I've been handed many shiny apps over the course of my consulting work, and I've never had a situation where the app was easy to jump right into and reason about. The code becomes unwieldy very fast, even when following best practices. If you have an app that you are going to spend enough time on to make it polished, and you want to make it widely available, in most cases it is more efficient to use industry standard user interface tools (JavaScript: React, Vue, etc.) and call R as necessary for calculations that must be done in R. Most of the R/Shiny apps in production that I have seen feel sluggish and are often buggy. You will find yourself pretty quickly wanting to do things with your UI that are very unnatural in Shiny and result in solutions that feel like hacks or begin to require knowledge of JavaScript. Things such as conditionally showing inputs, interactions between inputs (disable an input until another input has been selected), etc. Sometimes it's good to consider alternatives -- R Markdown, etc. --- class: center, middle # What are some alternatives to R-Shiny? ![:scale 65%](img/comparison.png) ??? Focus on Dash, Streamlit, and Shiny. Shiny is the only R language dashboard tool on this chart. If anyone has a Python background, we highly recommend Streamlit. If building --- class: center, middle, inverse # Making our first R-Shiny dashboard ??? We'll learn by example. We will of course not be able to go into detail about everything, but we should be able to give you a feel for what a working app can look like which can act as a basis for you to explore your own use cases. --- class: left, middle, my-one-page-font # Thanks to [shinyintro](https://debruine.github.io/shinyintro/first-app.html) by [Lisa Debruine](https://github.com/debruine) - We recommend her book for a smooth entrance into Shiny - For our app we will use RStudio's ['shinydashboard'](https://rstudio.github.io/shinydashboard/) package ??? --- class: left, middle, my-one-page-font # Using the `shinydashboard` package ### The [`shinydashboard`](https://rstudio.github.io/shinydashboard/) package is a collection of functions that make it easy to create dashboards. ### You can find the code for this example at our [RShinyDashboards Repository](https://github.com/ki-tools/RShinyDashboards) ??? shinydashboard makes it easy to use Shiny to create dashboards It is built by RStudio. - https://rstudio.github.io/shinydashboard/ - https://github.com/rstudio/shinydashboard --- class: left, middle # Our first R Shiny Dashboard __Our Design Goal:__ Give our user the opportunity to see various daily COVID-19 metrics for selected locations over a specified time window using a dataset from [Our World in Data](https://github.com/owid/covid-19-data). .center[ ![:scale 80%](img/dashboard_example_global.png) ] --- class: left, middle # Data Source: [Our World in Data](https://github.com/owid/covid-19-data) ```R # A tibble: 161,909 × 67 iso_code continent location date total_cases new_cases new_cases_smoothed total_deaths new_deaths
1 AFG Asia Afghanistan 2020-02-24 5 5 NA NA NA 2 AFG Asia Afghanistan 2020-02-25 5 0 NA NA NA 3 AFG Asia Afghanistan 2020-02-26 5 0 NA NA NA 4 AFG Asia Afghanistan 2020-02-27 5 0 NA NA NA 5 AFG Asia Afghanistan 2020-02-28 5 0 NA NA NA 6 AFG Asia Afghanistan 2020-02-29 5 0 0.714 NA NA 7 AFG Asia Afghanistan 2020-03-01 5 0 0.714 NA NA 8 AFG Asia Afghanistan 2020-03-02 5 0 0 NA NA 9 AFG Asia Afghanistan 2020-03-03 5 0 0 NA NA 10 AFG Asia Afghanistan 2020-03-04 5 0 0 NA NA # … with 161,899 more rows, and 58 more variables: new_deaths_smoothed
, total_cases_per_million
, # new_cases_per_million
, new_cases_smoothed_per_million
, total_deaths_per_million
, # new_deaths_per_million
, new_deaths_smoothed_per_million
, reproduction_rate
, # icu_patients
, icu_patients_per_million
, hosp_patients
, hosp_patients_per_million
, # weekly_icu_admissions
, weekly_icu_admissions_per_million
, weekly_hosp_admissions
, # weekly_hosp_admissions_per_million
, new_tests
, total_tests
, # total_tests_per_thousand
, new_tests_per_thousand
, new_tests_smoothed
, … ``` --- class: left, middle # Starting with our chart (part 1) ```R # load packages and read data for chart library(ggplot2) library(readr) library(dplyr) library(snakecase) d <- read_csv("https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/owid-covid-data.csv") # our potential inputs variable <- "new_deaths_smoothed_per_million" locations <- c("Africa", "Asia", "Europe", "North America", "Oceania", "South America") date_range <- as.Date(c("2020-01-01", "2022-12-31")) ggplot(data = filter(d, location %in% locations, date >= date_range[1], date <= date_range[2]), aes_string("date", variable, color = "location")) + geom_point(size = 0.8, alpha = 0.8) + geom_line() + theme_bw() + labs( x = "Date", y = to_title_case(variable), color = "Location" ) + guides(color = guide_legend( override.aes = list(lty = NA, size = 5, shape = 15))) ``` ??? Before we get into Shiny code, let's write the code for our key chart in the display. Highlight that we should 1. think about the visuals, summaries, and tables we want to leverage and why we want to display them. 2. write the code in an R script so we see what we want in the chart and what variables we might vary. --- class: left, middle # The Shiny app basics Every Shiny app has two key elements: `ui` and `server`. When using `shinydashboard` the layout of our `ui` is handled for us and we just need to stick the appropriate controls and outputs in the right place. ```R header <- dashboardHeader(CODE FOR THE HEADER UI) sidebar <- dashboardSidebar(CODE FOR THE INPUTS) body <- dashboardBody(CODE FOR THE RESULTS OUTPUTS) ui <- dashboardPage( skin = 'purple', header, sidebar, body ) ``` ```R server <- function(input, output) { THE CODE THAT CREATE CHARTS, TABLES, AND TEXT FROM INPUTS } ``` ```R shinyApp(ui, server) ``` The ['app_global.R'](app/app_global.R) code for our dashboard is in the `app` folder of our [repository](https://github.com/ki-tools/RShinyDashboards) ??? Focus on how shinydashboard leverages `dashboardPage()` and the respective sections of the dashboard `dashboardHeader()`, `dashboardSidebar()`, and `dashboardBody()` which is a bit different that the base functionality of shiny. Might have them download the `app_global.R` script. --- class: left, middle # Using R-Studio for app development Start R-Studio and create a new script `app_global.R` in a new `project` or `folder`. Once we have our script built we will click on `Run App`. .center[ ![:scale 50%](img/04-rstudio-interface.png) ] ??? At this point, if you would like to test out the entire app, you can copy and paste the code from ['app_global.R'](app/app_global.R) into RStudio and click "Run App". --- class: left, middle __Building the interface incrementally:__ _Copy this code and paste it into `app_global.R`_ ```R # Setup ---- library(shiny); library(shinydashboard); library(readr); library(dplyr) library(ggplot2); library(stringr); library(plotly); library(snakecase) header <- dashboardHeader(title = "COVID-19 Global Data Dashboard", titleWidth = 400) # Define UI ---- sidebar <- dashboardSidebar(width = 400, actionButton("load", label = "Import data from Our World in Data"), selectInput("variable", "What variable", "(data not loaded)", multiple = FALSE, selected = "(data not loaded)"), selectInput("location", "Which location?", "(data not loaded)", multiple = TRUE, selected = "(data not loaded)"), dateRangeInput("daterange", "Date range:", start = "2020-01-01", end = "2022-12-31"), actionButton("makeplot", label = "Explore Visualization") ) body <- dashboardBody( box(title = "Pandemic time-series", solidHeader = TRUE, width = 16, collapsible = TRUE, plotlyOutput("timeseries", height = 500, width = "auto")), box(title = "Total reported cases (World)", solidHeader = TRUE, width = 6, div(style = "font-size:50px", textOutput("casetotal"))), box(title = "Total reported deaths (World)", solidHeader = TRUE, width = 6, div(style = "font-size:50px", textOutput("deathtotal"))) ) # Run the application ---- ui <- dashboardPage(skin = "purple", header, sidebar, body) server <- function(input, output, session) {} shinyApp(ui, server) ``` ??? However, for the purpose of this workshop we are going to build the app sequentially. We already created the plot. Now we need to build the UI and figure out how to plug the plot and its inputs in. This code has a lot happening that we don't have the time to discuss in detail. You can see links to each of the functions used at `app/readme.md`. The functions have pretty clear names that allow you to see its functionality by the name. Some of the functions are html wrappers. --- class: left, middle # Inputs - ### `actionButton("load", ...)`: a button that will load the data - ### `selectInput("variable", ...)`: a dropdown menu listing all of the variables that can be plotted - ### `selectInput("location", ...)`: a dropdown menu listing all of the geographic locations we can plot data for - ### `dateRangeInput("daterange", ...)`: a date range picker - ### `actionButton("makeplot", ...)`: a button that when pressed will create the visualization for the specified paramters ### These are all __inputs__. Their values will be made available in the app server as `input$load`, `input$variable`, etc. --- class: left, middle # Outputs - ### `box()`: a UI element in the shinydashboard package that designates a content area in the dashboard - ### `plotlyOutput("timeseries", ...)`: specifies where the time series visualization will be placed - ### `textOutput("casetotal")`: specifies where the case total for the specified location(s) will go - ### `textOutput("deathtotal")`: specifies where the death total for the specified location(s) will go ### These are all __outputs__. Their values will be made available in the app server as `output$timeseries`, `output$casetotal`, `output$deathtotal`. --- class: left, middle # Server: Add body elements ### Include these [`reactiveVal()`](https://shiny.rstudio.com/reference/shiny/1.3.0/reactiveVal.html) assignments before the `server` function. ```R covid_data <- reactiveVal() plot_data <- reactiveVal() ``` ### A "reactive value" is a special object in Shiny that causes other elements of the application to update, or "react" when the variable is initialized or changed. ### A reactive value can be __set__ by calling it with an argument, e.g. `covid_data(my_data_frame)` ### A reactive value is __accessed__ by calling it with no arguments, e.g. `covid_data()` --- class: left, middle # Server: Add body elements (Data & Totals) ### Now include our data import event within the
`server <- function(input, output, session) { SERVER CONTENTS }`. ```R # Data ingestion and total number calculations. Set to work after `input$load` occurs from clicking on Import Data. observeEvent(input$load, { url <- "https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/owid-covid-data.csv" owid_data <- readr::read_csv(url) covid_data(owid_data) # This reactive item now has is composed of the COVID-19 data. # assigning them into the output object allows them to be called by the `ui` output$deathtotal <- renderText(owid_data %>% filter(location == "World", date == max(date)) %>% pull(total_deaths) %>% sum() %>% format(big.mark = ",")) output$casetotal <- renderText(owid_data %>% filter(location == "World", date == max(date)) %>% pull(total_cases) %>% sum() %>% format(big.mark = ",")) # update inputs to reflect options found in the data choices <- names(owid_data); names(choices) <- to_title_case(choices) updateSelectInput(session, "variable", choices = choices, selected = "total_cases_per_million") updateSelectInput(session, "location", choices = unique(owid_data$location), selected = "World") }) ``` ??? Highlight the `input` and 'output' functionality. How we can assign `render__` functions as a list element and get values from our ui using the `input$` construct. --- class: left, middle # Server components - ### `observeEvent(input$load, { ... })`: responds to the input "load" button -- in this case it executes the code in `{...}` whenever the load button is clicked - ### `covid_data(owid_data)`: sets the `covid_data` reactive value with OWID data - ### `output$deathtotal`: sets the "deathtotal" output to the default ("World") location's death total - ### `updateSelectInput(...)`: updates the options available in the dropdown menus now that the data has been loaded --- class: left, middle # Server: Add body elements (Chart) ```R observeEvent(input$makeplot, { # notice use of `input$` to pull the user input values into the function. # "if" stops outputs from being created if user hasn't clicked import if (length(covid_data()) != 0) { newdat <- covid_data() %>% filter(location %in% input$location, date >= input$daterange[1], date <= input$daterange[2]) # This reactive item now has is composed of the filtered data. plot_data(newdat) # notice the use of plot_data() as the data object to signal a reactive dataset. output$timeseries <- renderPlotly({ ggplot(data = plot_data(), aes_string("date", isolate(input$variable), color = "location")) + geom_point(size = 0.8, alpha = 0.8) + geom_line() + theme_bw() + labs( title = paste0("Progression of the pandemic from ", input$daterange[1], " to ", input$daterange[2]), x = "Date", y = isolate(to_title_case(input$variable)), color = "Location") + guides(color = guide_legend( override.aes = list(lty = NA, size = 5, shape = 15))) }) } }) ``` ??? May want to highlight how our ggplot changes to handle the shiny paradigm. The use of - `.data[['input$variable']]` - `input#daterange[1]` - `plotdata()` - all wrapped in `renderPlot()` --- class: left, middle # Server components - ### Our previous ggplot code is now wired to handle the inputs - ### `observeEvent(input$makeplot, { ... })`: responds to the input "Explore visualization" button -- executing the code in `{...}` whenever the load button is clicked - ### `plot_data(newdat)`: sets the `covid_data` reactive value with OWID data filtered to the currently-specified location(s) and date range - ### `output$timeseries <- renderPlotly(...)`: updates the plot output based on this data - ### `isolate(...)`: this keeps the plot from updating whenever inputs are changed (so that plot only updates when "Explore visualization" is clicked) --- class: left, middle # Our final dashboard You can see the complete script at [`app/app_global.R`](app/app_global.R) .center[ ![:scale 85%](img/dashboard_example_global.png) ] --- class: left, middle # Additional features ### These are features that would be nice to add to the app: - ### Automatically load the data - ### Or load only the parts of the data needed - ### Provide a progress indicator for the data download - ### Only allow up to 10 locations to be chosen at a time - ### When a user input is changed, gray out the plot and text output areas until "explore visualization" is pressed again - ### Don't allow the "to" date to be earlier than the "from" Date - ### etc. ??? Some of these can be done with a little work. Many can be done with a lot of work and probably some JavaScript. --- class: center, middle, inverse # A few tips for creating dashboards --- class: center, middle ![:scale 95%](img/know-user.png) --- class: my-one-page-font # Define your production goals - How often will it be used? - How reliable does it need to be? - What is the impact if it is innacurate? .center[ ![:scale 75%](img/production-fault-chain.png) ] ??? Based on [reference material from engineering-shiny.org](https://engineering-shiny.org/) --- class: my-one-page-font # Design with a purpose > To design without purpose is to design without a goal, without an objective or without a target. It's just design for design’s sake. Not nearly as fulfilling as design with a direction. Without purpose, design is just decoration—aesthetics without true meaning. > .right[_[THEIL](https://thiel.com/design-with-purpose-summary/)_] --- class: center, middle, inverse # How do I learn more? --- class: my-one-page-font # Online Reading Material - [Mastering Shiny](https://mastering-shiny.org/index.html) - [Learn Shiny](https://shiny.rstudio.com/tutorial/) - [Get Started w/ shinydashboard](https://rstudio.github.io/shinydashboard/get_started.html) - [Use shinydashboard](https://db.rstudio.com/best-practices/dashboards/#use-shinydashboard) - [Engineering Production-Grade Shiny Apps](https://engineering-shiny.org/index.html) - [shinyjs](https://deanattali.com/shinyjs/) - [shinydashboards](https://rstudio.github.io/shinydashboard/) - [Building Web Apps with R Shiny](https://debruine.github.io/shinyintro/index.html) - [stat545 Shiny Tutorial](https://stat545.com/shiny-tutorial.html) - [R markdown: The Definitive Guide - Shiny](https://bookdown.org/yihui/rmarkdown/shiny.html) - [YouTube: A Gentle Introduction to Creating R Shiny Webb Apps](https://www.youtube.com/watch?v=jxsKUxkiaLI) - [YouTube: Dynamic Dashboards with Shiny](https://www.youtube.com/watch?v=tmHh89VTanw) --- class: my-one-page-font # Short Courses - [Building Web Applications with Shiny in R](https://app.datacamp.com/learn/courses/building-web-applications-with-shiny-in-r) - [Shiny Fundamentals with R](https://app.datacamp.com/learn/skill-tracks/shiny-fundamentals-with-r) - [Building Data Apps with R and Shiny: Essential Training](https://www.linkedin.com/learning/building-data-apps-with-r-and-shiny-essential-training/build-test-and-deploy-apps-easily-in-shiny?u=2153100) - [Creating Interactive Presentations with Shiny and R](https://www.linkedin.com/learning/creating-interactive-presentations-with-shiny-and-r/welcome?u=2153100) - [Interactive Visualization with R](https://campus.sagepub.com/interactive-visualization-with-r-for-social-scientists) --- class: my-one-page-font # Shinyverse of R packages _There are so many packages available. [awesome-shiny](https://github.com/nanxstats/awesome-shiny-extensions) has an extensive list. Use the below list to start your journey._ - [shinydashboard](https://rstudio.github.io/shinydashboard/) - [shinydashboardPlus](https://shinydashboardplus.rinterface.com/) - [Shiny Themes](https://rstudio.github.io/shinythemes/) - [shinymanager](https://datastorm-open.github.io/shinymanager/) - [shiny.semantic](https://appsilon.github.io/shiny.semantic/) - [shiny.react](https://appsilon.github.io/shiny.react/) - [shiny.fluent](https://appsilon.github.io/shiny.fluent/) - [shiny sense](http://nickstrayer.me/shinysense/) --- class: center, middle, inverse # Thanks