class: title-slide, center, bottom # 08 - Interactive web applications with shiny ## Data Science with R · Summer 2021 ### Uli Niemann · Knowledge Management & Discovery Lab #### [https://brain.cs.uni-magdeburg.de/kmd/DataSciR/](https://brain.cs.uni-magdeburg.de/kmd/DataSciR/) .courtesy[📷 Photo courtesy of Ulrich Arendt] --- exclude: true class: middle ### [Access to hospital care in the US](http://colorado.rstudio.com:3939/content/188/) .pull-left60[ <img src="figures//08-example_hospital_care_2.png" width="100%" /> ] .pull-right40[ Interactive chloropleth map of access to hospital care in US counties. Underserved counties are highlighted. <img src="figures//08-example_hospital_care_1.png" width="100%" /> ] --- name: beispiel-apps class: middle ### [Sales analytics dashboard of a bycicle shop](https://mdancho84.shinyapps.io/shiny-app/) .pull-left60[ Analysis and visualization of historic sale transactions, stratified by bike type, bike model and US state. <img src="figures//08-example_bycicle_sales_1.png" width="100%" /> ] .pull-right40[ <img src="figures//08-example_bycicle_sales_2.png" width="100%" /> ] --- class: middle ### [Prevention of hospital infections](https://hospinf.shinyapps.io/hospinf/#section-graphs) Frequency of hospital infections, use of antibiotic agents, antibiotic resistance, operations, ... over time <img src="figures//08-example_hospital_infections.png" width="80%" /> --- class: middle ### [Network visualiztion of biochemical processes](https://dgranjon.shinyapps.io/entry_level/) <img src="figures//08-example_calcium.png" width="100%" /> --- class: middle ### [Memory game app](https://dreamrs.shinyapps.io/memory-hex/) <img src="figures//08-example_memory.png" width="100%" /> --- class: middle ### [New Zealand tourism dashboard](https://mbienz.shinyapps.io/tourism_dashboard_prod/) <img src="figures//08-example_tourism.png" width="75%" /> --- class: middle ### [Shiny gallery: user showcases & demos](https://shiny.rstudio.com/gallery/) <img src="figures//08-shiny_user_showcase.png" width="80%" /> --- class: middle ## ["Hello, World!" app](http://shiny.rstudio.com/gallery/faithful.html) <img src="figures//08-faithful_app.png" width="100%" /> ??? - histogram shows distribution of eruptions durations of a famous geyser in Yellowstone NP (Old Faithful) - bimodal distribtion - user can modify the plot in several ways - number of bins in histogram (dropdown-menu) - whether to show individual obs. as rugs (checkbox) - whether to augment the histogram with a density estimate curve - dropdown-menu pops up -> bandwidth adjustment - on the webserver, a R session runs that handles the user‘s inputs and updates the plot accordingly - Demo with the app's code in the sidebar -> 2 components - Script with user interface (front-end) -> necessary HTML to render the site - Script for functionality (back-end) -> instructions to draw the plot --- name: grundlagen ## What is Shiny? .pull-left70[ - `R` package to create **interactive web apps** without deep knowledge of HTML, CSS and JavaScript - based on **reactive programming**: manipulable **inputs** trigger automatic re-computation of **outputs** ### Application scenarios - Dashboards that allow users to interactively toggle between compact at-a-glace summary and specific performance values → customizable to the level of detail the users need - present complex models to a non-technical audience with interactive visualizations - replace very long PDF documents or printed reports: allow users to drill down to sections they are most interested in <!-- - **`R`-Session**, die im Hintergrund läuft, stämmt sämtliche Berechnungen --> <!-- - Aufbau: --> <!-- - **User Interface** (ui-Komponente) --> <!-- - **Server Instructions** (server-Komponente) --> ] .pull-right30[ <img src="figures//08-shiny_logo.png" width="100%" /> ] ??? - web app: runs in any web browser (also mobile), user does not need to have R installed - no installation required: all computers have a browser - reactive programming vs. event-driven programming: - automatically track dependencies between an input (eg drop-down menu) and outputs (eg plots) that depend on that input - whenever an input changes, Shiny can automatically update all related outputs without you having to define specific event handlers <!-- * Create dashboards that track important performance indicators, and facilitate --> <!-- drill down into surprising results. --> <!-- * Replace hundreds of pages of PDFs with interactive apps that allow the --> <!-- user to jump to the exact slice of the results that they care about. --> <!-- * Communicate complex models to a non-technical audience with informative --> <!-- visualisations and interactive sensitivity analysis. --> <!-- * Provide self-service data analysis for common workflows where replacing --> <!-- email requests with a Shiny app that allows people to upload their own --> <!-- data and perform standard analyses. --> <!-- * Create interactive demos for teaching statistics and data science concepts --> <!-- that allow learners to tweak inputs and observe the downstream effects of --> <!-- these changes in an analysis. --> --- ## Minimal template of a Shiny app ```r library(shiny) ui <- fluidPage() # controls layout & appearance (e.g. `bootstrapPage()`, `fluidPage()`, `fixedPage()`) server <- function(input, output, session) {} # function that maps inputs to outputs shinyApp(ui = ui, server = server) # runs the app ``` --- ## ["Hello, World!" app](http://shiny.rstudio.com/gallery/faithful.html) <img src="figures//08-faithful_app.png" width="100%" /> --- class: middle <img src="figures//08-faithful_code.png" width="90%" style="display: block; margin: auto;" /> --- ## ui.R <img src="figures//08-faithful_code_1.png" width="100%" /> ??? - dropdown menu for the number of bins in the histogram - a checkbox to indicate whether individual observations should be displayed as rugs (tiny lines) along the x-axis - a checkbox to indicate whether the estimated probability density function should be displayed as curve on top of the histogram - sole outpt: a plot container for the histogram - ui objects are comma-separated --- ## Example input: dropdown list <img src="figures//08-faithful_code_2.png" width="80%" /> --- ## Example input: dropdown list <img src="figures//08-faithful_inputs.png" width="100%" /> **Mandatory arguments** of input widgets: - `inputId`: unique identifier - `label`: descriptive label **Input-specific arguments** (see `?selectInput)`: - `choices`: list of values - `selected`: selected value at the start - `multiple`: whether the selection of multiple values is allowed - ... --- ## Input widgets <img src="figures//08-inputs.png" width="90%" style="display: block; margin: auto;" /> .footnote[[Shiny Input Widgets](https://shiny.rstudio.com/tutorial/written-tutorial/lesson3/)] --- ### Inputs have values... <img src="figures//08-widget_gallery.png" width="90%" style="display: block; margin: auto;" /> .footnote[[Widget gallery](http://shiny.rstudio.com/gallery/widget-gallery.html)] --- ## Outputs <img src="figures//08-outputs.png" width="65%" style="display: block; margin: auto;" /> .pull-left60[ .font90[ **Mandatory argument**: - `outputId`: unique identifier **output-specific arguments** (see `?plotOutput`): - `width`, `height`: width and height as CSS command - ... ] ] .pull-right40[ ```r textOutput() # text htmlOutput() # raw HTML imageOutput() # image plotOutput() # plot tableOutput() # table uiOutput() # Shiny UI element ``` ] ??? - all outputs are generated with *Output() - This code generates the HTML container for the histogram - If we would run this code without further instructions, we would not see the plot - We have to say explicitly what sort of plot we want to show -> server - Small recap up to this point: - Basic architecture of a Shiny app: ui and server part - UI elements (dropdown-menus, radiobuttons, plots and tables) are specified in UI - Create Reactive Inputs with input functions (selectInput, sliderInput) - Create Reactive outputs with output functions (plotOutput, tableOutput) - Inside the server functions, inputs are used to create outputs --- ## server.R .pull-left[ <img src="figures//08-server.png" width="90%" /> ] .pull-right[ .orange[ Function `server()` takes 2 arguments: - `input`: list of input objects (`input$<inputId>`) - `output`: list of output objects (`output$<outputId>`) ] .red[ Output objects are saved as `output$<outputId>`. ] .blue[ Output objects are created with `render*()` functions. ] .green[ Input objects are called using `input$<inputId>`. ] ] --- ## Access current value of an input in server.R <img src="figures//08-inputs_server_ui.png" width="90%" style="display: block; margin: auto;" /> --- ## Use an output from ui.R <img src="figures//08-outputs_server_ui.png" width="90%" style="display: block; margin: auto;" /> --- ## Render functions .pull-left60[ Output objects are created within `render*()` functions. <img src="figures//08-render.png" width="65%" style="display: block; margin: auto;" /> ] .pull-right40[ Example: `renderPlot(boxplot(iris$Sepal.Length))` ```r renderText() renderPrint() renderImage() renderPlot() renderTable() renderUI() ``` ] ??? - curly brackets are necessary for multi-line commands --- ## Reactivity <img src="figures//08-reactivity.png" width="90%" style="display: block; margin: auto;" /> Whenever an input objects is called within a `render*()` function via `input$<inputId>`, Shiny will automatically the associated output object. --- ## Inputs & Outputs .pull-left[ **Reactive Programming**: when an input changes, all related outputs are automatically updated. .red[Compare this to the normal behavior of `R`:] ```r x <- 1 y <- x + 2 x <- 9 # What value does y have? 3 or 11? ``` ] .pull-right[ <img src="figures//08-inputs_outputs.png" width="90%" /> ] --- class: middle ## Recap: basic structure of a Shiny app - **ui** component and **server** component - **ui** contains **reactive inputs** (dropdown lists, radio buttons, ...) and **reactive outputs** (charts, tables, ...) - reactive inputs are placed into the ui using **input functions** (`selectInput()`, `radioButtons()`, ...) - reactive output containers are placed into the ui using **output functions** (`plotOutput()`, `tableOutput()`, ...) - **server** contains code to create outputs, whose re-computation is triggered by one of the associated inputs - output values (plots, tables) are generated within **render functions** (`renderPlot()`, `render(Table)`, ...) --- name: modularisierung ## Code modularization Often, we need intermediate results (computed from reactive inputs) for multiple render functions. <img src="figures//08-app-02_modularity.png" width="60%" /> --- ## Code modularization .font90[ ```r library(shiny) library(ggplot2) library(dplyr) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), plotOutput(outputId = "plot"), verbatimTextOutput(outputId = "summary") ) server <- function(input, output) { output$plot <- renderPlot({ * x <- tibble(x = rnorm(input$slider)) ggplot(x, aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) }) output$summary <- renderPrint({ * x <- rnorm(input$slider) summary(x) }) } shinyApp(ui, server) ``` ] 🤔 _Which 2 problems does this code have?_ -- → Histogram and summary console output do not show the same data. → **Code duplication** leads to problems in the long run. For example, if `runif()` shall be used instead of `rnorm()`, we would have to change the code at 2 places. -- ⟶ Solution: we create a **reactive object** for random values which can be used by both render functions. --- ## Code modularization ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), plotOutput(outputId = "plot"), verbatimTextOutput(outputId = "summary") ) server <- function(input, output) { * values <- rnorm(input$slider) output$plot <- renderPlot({ * ggplot(tibble(x = values), aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) }) output$summary <- renderPrint({ * summary(values) }) } shinyApp(ui, server) ``` .red[.font90[.remark-code[Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)] ] ] --- ## Code modularization → Enclose with `reactive()`: `values <- reactive(rnorm(input$slider))` We can access reactive values in reactive functions using `values()` (.red[mind the parentheses]): ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), plotOutput(outputId = "plot"), verbatimTextOutput(outputId = "summary") ) server <- function(input, output) { * values <- reactive({rnorm(input$slider)}) output$plot <- renderPlot({ * ggplot(tibble(x = values()), aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) }) output$summary <- renderPrint({ * summary(values()) }) } shinyApp(ui, server) ``` --- name: reaktivitaet ## Prevent re-computations <img src="figures//08-isolate-example.gif" width="85%" style="display: block; margin: auto;" /> --- ## Prevent re-computations ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), textInput(inputId = "text", label = "histogram title", value = "fancy title"), plotOutput(outputId = "plot") ) server <- function(input, output) { output$plot <- renderPlot({ ggplot(tibble(x = rnorm(input$slider)), aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) + * labs(title = input$text) }) } shinyApp(ui, server) ``` --- ## Prevent re-computations → Enclose with `isolate()`: `isolate({input$title})` returns the result as non-reactive value. ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), textInput(inputId = "text", label = "histogram title", value = "fancy title"), plotOutput(outputId = "plot") ) server <- function(input, output) { output$plot <- renderPlot({ ggplot(tibble(x = rnorm(input$slider)), aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) + * labs(title = isolate({input$text})) }) } shinyApp(ui, server) ``` The histogram will only be re-rendered if the value of the slider changes. --- ## Trigger computations with reactive values Example: action button <img src="figures//08-observeEvent.png" width="100%" /> --- ## Trigger computations with reactive values ```r library(shiny) ui <- fluidPage( * actionButton(inputId = "button", label = "Go!") ) server <- function(input, output) { * observeEvent(input$button, { * print(input$button) * }) } shinyApp(ui, server) ``` → ... with `observeEvent(<input$inputId>, <CODE>)` Only if `<input$inputId>` changes, the `<CODE>` will be run! This means, if `<input$otherInputId>` (that is used within `<CODE>`) changes, re-computation of `<CODE>` will not be triggered. --- ## Create an reactive observer ```r library(shiny) ui <- fluidPage( * actionButton(inputId = "button", label = "Go!") ) server <- function(input, output) { * observe(print(input$button)) } shinyApp(ui, server) ``` In contrast to `reactive()`, `observe()` does not return a value. It is called for its _side effects_. --- ## Delay re-computations <img src="figures//08-app05.png" width="65%" style="display: block; margin: auto;" /> --- ## Delay re-computations ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), textInput(inputId = "text", label = "histogram title", value = "fancy title"), actionButton(inputId = "button", label = "Go!"), plotOutput(outputId = "plot") ) server <- function(input, output) { * rp <- eventReactive(input$button, { * ggplot(tibble(x = rnorm(input$slider)), aes(x)) + * geom_histogram(color = "black", fill = "royalblue", bins = 10) + * labs(title = input$text) * }) * output$plot <- renderPlot(rp()) } shinyApp(ui, server) ``` Template: `rv <- eventReactive(<input$inputId>, <CODE>)` Only if `<input$inputId>` changes, `<CODE>` will be run and the output will be stored as `rv`. Similar to `reactive()`, you can access reactive values created by `eventReactive()` using `rv()` (mind the parentheses). --- ## Manipulable reactive values Create a list of reactive values that can later be modified by input changes. Template: `data <- reactiveValues(x1 = ..., x2 = ...)` <img src="figures//08-app-06.png" width="65%" style="display: block; margin: auto;" /> --- ## Manipulable reactive values ```r library(shiny) library(ggplot2) library(tibble) ui <- fluidPage( sliderInput(inputId = "slider", label = "Number of random values", min = 10, max = 150, step = 10, value = 50), * actionButton(inputId = "normal", label = "normal distribution"), * actionButton(inputId = "unif", label = "uniform distribution"), plotOutput(outputId = "plot") ) server <- function(input, output) { * data <- reactiveValues(x = rnorm(100)) * observeEvent(input$normal, data$x <- rnorm(input$slider)) * observeEvent(input$unif, data$x <- runif(input$slider)) output$plot <- renderPlot({ ggplot(tibble(x = data$x), aes(x)) + geom_histogram(color = "black", fill = "royalblue", bins = 10) }) } shinyApp(ui, server) ``` --- ## Recap: reactivity - `render*()`: create **reactive outputs** that will be re-computed each time one of the values of the inputs they depend on is changed -- - `reactive()`: create a **reactive expression**, that will be re-computed each time each time one of the values of the inputs they depend on is changed -- - `isolate()`: **prevent the reactive binding** between an input and an output -- - `observeEvent(x, ...)`: code will be executed only if the value of the reactive expression `x` changes. Does _not_ return a value. -- - `eventReactive(x, ...)`: code will be executed only if the value of a reactive expression `x` changes. Does return a value. -- - `observe()`: code is executed in a reactive context. Does _not_ return a value. -- - `reactiveValues()`: creates a list of reactive values, which can be modified later. --- name: misc ## Layouts .pull-left[ <img src="figures//08-ui_layout_fluidrow.png" width="90%" /> <img src="figures//08-ui_layout_sidebar_split.png" width="90%" /> ] .pull-right[ <img src="figures//08-ui_layout_flowlayout.png" width="90%" /> <img src="figures//08-ui_layout_tabset_navbar.png" width="90%" /> ] .footnote[ 📚 [Application layout guide](https://shiny.rstudio.com/articles/layout-guide.html) ] --- ## HTML tags .pull-left[ - Shiny apps are websites. - Websites are written in HTML. - Shiny apps are written in `R`. - HTML element can be inserted with `tags$<element>()` into the UI. .footnote[ 📚 [Customize your UI with HTML](https://shiny.rstudio.com/articles/html-tags.html) ] ] .pull-right[ <img src="figures//08-html_tags.png" width="100%" /> ] --- class: center, middle .large[`tags$h1("Title")` .green[⬌] `<h1>Title</h1>`] -- .large[`tags$strong("Boldface")`] .large[.green[⬌]] .large[`<strong>Boldface</strong>`] -- .large[`tags$a("URL", href = "https://www.google.com")` .green[⬌] `<a href="https://www.google.com">URL</a>`] --- class: middle <img src="figures//08-html_tags_h1.png" width="100%" /> --- class: middle <img src="figures//08-html_tags_misc.png" width="100%" /> --- ## Dashboards with [AdminLTE](https://adminlte.io/) theme <img src="figures//08-adminlte.png" width="50%" style="display: block; margin: auto;" /> Features: [Notification Menus](https://rstudio.github.io/shinydashboard/structure.html#notification-menus), [Task Menus](https://rstudio.github.io/shinydashboard/structure.html#task-menus), [infoBoxes](https://rstudio.github.io/shinydashboard/structure.html#infobox), [valueBoxes](https://rstudio.github.io/shinydashboard/structure.html#valuebox), ... .footnote[ 📚 [Getting Started Guide](https://rstudio.github.io/shinydashboard/index.html), [Webinar Dynamic Dashboards with Shiny](https://www.rstudio.com/resources/webinars/dynamic-dashboards-with-shiny/), [Building Dashboards with Shiny Tutorial](https://www.rstudio.com/resources/videos/building-dashboards-with-shiny-tutorial/) ] --- ## App Deployment | Pros | Cons :----- | :--- | :---- local | + free<br>+ data remains local | - every user has to install R and shiny<br>- maintenance [shinyapps.io](https://www.shinyapps.io/) | + free up to 5 apps / account<br>+ no installation or hardware necessary<br>+ minimal maintenance<br>+ push button publishing | - tariffs with authentication 99+ USD/Monat<br>- data in external cloud [Shiny Server](https://rstudio.com/products/rstudio/#rstudio-server) | + free (open source license)<br>+ data remains behind own firewall<br>+ authentication | - deployment and maintenance require more effort compared to shinyapps.io [RStudio Connect](https://www.rstudio.com/products/connect/) | + easy "push button" deployment<br>+ data remain behind own firewall<br>+ authentication | - more expensive than shinyapps.io [electricShine](https://chasemc.github.io/electricShine/) <br>(unofficial) | + free<br>+ local offline app (.exe) | - requires more effort to set up compared to shinyapps.io<br>- no official support<br>- large file size (portable R installation) .footnote[ 📚 [What is the difference between RStudio Connect, Shiny Server Pro, and Shinyapps.io?](https://support.rstudio.com/hc/en-us/articles/217240558-What-is-the-difference-between-RStudio-Connect-Shiny-Server-Pro-and-Shinyapps-io) ] ??? Electron enables executing cross platform desktop application using the Chromium web browser and the node.js framework. https://www.youtube.com/watch?time_continue=821&v=ARrbbviGvjc https://github.com/ksasso/useR_electron_meet_shiny --- exclude: true ## More Shiny & materials .footnote[ 📚 Curated list of ["Awesome Shiny Extensions"](https://github.com/nanxstats/awesome-shiny-extensions) ] .pull-left80[ Extension packages: - [`shinyWidgets`](https://github.com/dreamRs/shinyWidgets): additional input widgets - [`shinycssloaders`](https://github.com/andrewsali/shinycssloaders): add loading animations - [`shinyjs`](https://github.com/daattali/shinyjs): extend Shiny apps with basic JavaScript - [`reactlog`](https://github.com/rstudio/reactlog): visually debug Shiny apps ] .pull-right20[ <img src="figures//08-shiny_logo.png" width="100%" /> ] - Hadley Wickham. [Mastering Shiny](https://mastering-shiny.org/). O'Reilly, 2020. Draft version. - [Shiny introductory webinar](https://shiny.rstudio.com/tutorial/) - [Shiny user showcase](https://www.rstudio.com/products/shiny/shiny-user-showcase/) - Business Science. [The Shiny AWS Book: A proven process for deploying applications in the Cloud with Shiny and AWS](https://business-science.github.io/shiny-production-with-aws-book/). - [Shiny cheat sheet](https://github.com/rstudio/cheatsheets/raw/master/shiny.pdf) --- class: last-slide, center, bottom # Thank you! Questions? .courtesy[📷 Photo courtesy of Stefan Berger]