background-image: url("img/logo_padded.001.jpeg") background-position: left background-size: 60% class: middle, center, .pull-right[ <br> ## .base_color[`shiny`] ## .base_color[Applications] <br> #### .navy[Kelly McConville] #### .navy[ Stat 108 | Week 7 | Spring 2023] ] --- ## Announcements * No sections this week! * Get Project 1 on Wed. ************************ ## Week's Goals .pull-left[ **Mon Lecture** * Interactive web applications with `shiny`. ] .pull-right[ **Wed Lecture** * Dashboarding with `flexdashboard`. ] --- ### Live Coding... Together + You are going to do some live coding with me. + If you miss a step, get stuck, or just need some help, type your question into #q-and-a. * If you do miss anything: * I will put all the apps we create today in the shared folder. * Remember that the lectures are recorded. --- ## Interactivity with `shiny` `shiny`: Framework for creating web applications * We will go from R code in a script file to an interactive web page. * Let's look at some examples! + [Forest Service](https://ncasi-shiny-tools.shinyapps.io/Counties/) + [Interactive Word Cloud](https://shiny.rstudio.com/gallery/word-cloud.html) + [Movie Explorer](https://shiny.rstudio.com/gallery/movie-explorer.html) + [The app we are going to create today](https://kelly-mcconville.shinyapps.io/firstapp/) --- ## Main Components In the example apps, what features of the apps did we interact with? -- In the example apps, what changed based on our selections? -- * **Inputs**: What user manipulates + Text + Sliders + Dropdown menus + Action buttons * **Output**: What changes based on user's selections + Graphs + Maps + Tables + Text * Use **reactive programming** + Update outputs when inputs change --- ## Main Components * **UI**: User interface + Defines how your app **looks** -- * **Server function** + Defines how your app **works** -- ```r library(shiny) # User interface ui <- fluidPage() # Server function server <- function(input, output){} # Creates app shinyApp(ui = ui, server = server) ``` --- ## App Building Workflow Starting a New App: * Create a folder called "ShinyApps". * Within "ShinyApps", create a folder called "insert useful name". * Open a script file (not an Rmd) and save it as "app.r" within the "insert useful name" folder. -- Revising an App: * Write some code in "app.r". * Click "Run App". + Notice what happens in the console. * Experiment with the app. * Stop the app. * Repeat. -- Good habit early on: * Write static code somewhere (in a .r or .Rmd) before you start the app. + Modify to be reactive when put in "app.r". **Let's practice together!** --- ### First App We want to create an app where someone can type in names from Stat 108 (or any names) and see how their popularity compares. * First write some static code that works. ```r library(tidyverse) # Smaller dataset babynames <- read_csv("https://raw.githubusercontent.com/harvard-stat108s23/materials/main/data/babynames_stat108.csv") names <- c("Kelly", "Christina", "Ian", "Isabella") dat_names <- babynames %>% group_by(year, name) %>% summarize(n = sum(n)) %>% group_by(year) %>% mutate(prop = n/sum(n)) %>% filter(name %in% names) ``` --- ### First App We want to create an app where someone can type in names from Stat 108 (or any names) and see how their popularity compares. * Input: <!-- Text --> * Output: <!-- Graph --> .pull-left[ ```r ggplot(data = dat_names, mapping = aes(x = year, y = prop, color = name)) + geom_line(linewidth = 2) ``` ] .pull-right[ <img src="stat108_wk07mon_files/figure-html/babynames1-1.png" width="768" style="display: block; margin: auto;" /> ] --- ## Base Components ```r library(shiny) # User interface ui <- fluidPage() # Server function server <- function(input, output){} # Creates app shinyApp(ui = ui, server = server) ``` --- ## Libraries * Make sure to put all the necessary libraries at the top of your app.R file. + Like Rmds, our apps must be self-contained. ```r # Libraries library(shiny) library(tidyverse) ``` --- ## Setting up the Room .pull-left[ The `ui` functions generate HTML code for the web page. * `fluidPage()`: Layout function * `titlePanel()`: Adds title * `sidebarLayout()`: Splits app into side panel and main panel. ] .pull-right[ <div class="figure" style="text-align: center"> <img src="img/sidebarLayout.png" alt="Source: Dean Attali" width="100%" /> <p class="caption">Source: Dean Attali</p> </div> ] --- ## Setting up the Room .pull-left[ The `ui` functions generate HTML code for the web page. * `fluidPage()`: Layout function * `titlePanel()`: Adds title * `sidebarLayout()`: Splits app into side panel and main panel. ] .pull-right[ ```r # User interface ui <- fluidPage( titlePanel(title = "Insert title"), sidebarLayout( sidebarPanel( ), mainPanel( ) ) ) ``` ] --- ## Input: Text * `textInput()`: Allow user to interact with the app by providing text ```r # User interface ui <- fluidPage( titlePanel("Which Stat 108 name is most popular?"), sidebarLayout( sidebarPanel( # Create a text input widget textInput(inputId = "names", label = "Enter Stat 108 names here", value = "Kelly"), p("Put a single space between the names.") ), mainPanel( ) ) ) ``` --- ## Output: Graph * `plotOutput()`: Tells Shiny where to render the graph. ```r # User interface ui <- fluidPage( titlePanel("Which Stat 108 name is most popular?"), sidebarLayout( sidebarPanel( # Create a text input widget textInput(inputId = "names", label = "Enter Stat 108 names here", value = "Kelly"), p("Put a single space between the names.") ), mainPanel( plotOutput(outputId = "graph") ) ) ) ``` --- ## UI Controls * All these UI functions are just a way for us to generate HTML (without needing to learn HTML). ```r textInput(inputId = "names", label = "Enter Stat 108 names here", value = "Kelly") ```
Enter Stat 108 names here
--- ## Bringing it to Life! Two important arguments for the `server()` function: * `input` is a named `list` that controls the input widgets. + EX: `input$names` controls the `textInput()` widget * `output` is a named `list` that controls the objects to display. + EX: `output$graph` ```r server <- function(input, output){ } ``` --- ## Bringing it to Life! ```r server <- function(input, output){ output$graph <- renderPlot({ dat_names <- babynames %>% group_by(year, name) %>% summarize(n = sum(n)) %>% group_by(year) %>% mutate(prop = n/sum(n)) %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= 1980) ggplot(data = dat_names, mapping = aes(x = year, y = prop,color = name)) + geom_line(linewidth = 2) }) } ``` --- ## First App! * Let's make sure we got all the pieces into our app and then interact with it. * Questions? --- ### Let's Add One More Output ```r server <- function(input, output){ output$graph <- renderPlot({ ... }) output$table <- renderDT({ dat_names <- babynames %>% group_by(year, name) %>% summarize(n = sum(n)) %>% group_by(year) %>% mutate(prop = n/sum(n)) %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= 1980) dat_names %>% group_by(name) %>% summarize(count = sum(n)) %>% arrange(desc(count)), filter = "top" }) } ``` --- ### Need to Also Place it in the UI ```r # User interface ui <- fluidPage( titlePanel("Which Stat 108 name is most popular?"), sidebarLayout( sidebarPanel( # Create a text input widget textInput(inputId = "names", label = "Enter Stat 108 names here", value = "Kelly"), p("Put a single space between the names.") ), mainPanel( plotOutput(outputId = "graph"), DTOutput(outputId = "table") ) ) ) ``` #### And to load the `DT` package ```r library(DT) ``` --- ## Reactive Expressions * How is our code redundant? * Let's try to fix it! * Can't create a static data frame. + Need it to be **reactive**! --- ## Reactive Expressions ```r dat_names <- reactive({ babynames %>% group_by(year, name) %>% summarize(n = sum(n)) %>% group_by(year) %>% mutate(prop = n/sum(n)) %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= 1980) }) ``` --- ## Reactive Expressions * Call reactive expressions like you would a function with no arguments. ```r output$graph <- renderPlot({ ggplot(data = dat_names(), mapping = aes(x = year, y = prop, color = name)) + geom_line(linewidth = 2) }) ``` --- ## Detour: [Clean Up the DataTable](https://shiny.rstudio.com/articles/datatables.html) ```r dat_names_agg <- reactive({ dat_names() %>% group_by(name) %>% summarize(count = sum(n)) %>% arrange(desc(count)) }) output$table <- renderDT({ datatable(dat_names_agg(), options = list(paging = FALSE, searching = FALSE, orderClasses = TRUE)) }) ``` --- ### Extracting out Imperative Code * Move code that doesn't need to be reactive outside the app. ### [Input Widgets](https://shiny.rstudio.com/gallery/widget-gallery.html) * Let's practice with: + `radioButtons()` + `sliderInput()` + `actionButton()` + `eventReactive()` + `selectizeInput()` --- ## `radioButtons()` * Within the `ui`: ```r radioButtons(inputId = "variable", label = "Variable of Interest", choices = c("n", "prop"), selected = "prop") ``` * Within the `server()` function: + Must handle the tidy eval issue ```r output$graph <- renderPlot({ ggplot(data = dat_names(), mapping = aes(x = year, y = .data[[input$variable]], color = name)) + geom_line(linewidth = 2) }) ``` --- ## `sliderInput()` * Within the `ui`: ```r sliderInput("year_range", "Range of Years:", min = min(babynames$year), max = max(babynames$year), value = c(1980,2010)) ``` * Within the `server()` function: ```r dat_names <- reactive({ babynames %>% group_by(year, name) %>% summarize(n = sum(n)) %>% group_by(year) %>% mutate(prop = n/sum(n)) %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= input$year_range[1], year <= input$year_range[2]) }) ``` --- ## `sliderInput()` * Within the `ui`: ```r sliderInput("year_range", "Range of Years:", min = min(babynames$year), max = max(babynames$year), value = c(1980, 2010), sep = "") ``` --- ### Controlling the Timing of Evaluation: `actionButton()` * This causes **all** input on the page to not send updates to the server until the button is pressed. * Within the `ui`: ```r actionButton("update", "Update Results!") ``` * Within the `server`: ```r dat_names <- eventReactive(input$update, { babynames %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= input$year_range[1], year <= input$year_range[2]) }) ``` * Why does the radio button still change the graph?? --- ### Controlling the Timing of Evaluation: `actionButton()` ```r dat_names <- eventReactive(input$update, { babynames %>% filter(name %in% c(unlist(str_split(input$names, " "))), year >= input$year_range[1], year <= input$year_range[2]) %>% mutate(y_var = .data[[input$variable]]) }) y_label <- eventReactive(input$update, input$variable) output$graph <- renderPlot({ ggplot(data = dat_names(), mapping = aes(x = year, y = y_var, color = name)) + geom_line(linewidth = 2) + labs(y = y_label()) }) ``` --- ## `selectizeInput()` * Within the `ui`: ```r selectizeInput(inputId = "names", label = "Enter Stat 108 names here", choices = NULL, multiple = TRUE) ``` * Within the `server()` function: ```r server <- function(input, output, session){ updateSelectizeInput(session, 'names', choices = unique(babynames$name), server = TRUE) } ``` --- ## Building Outputs Server-side: * Save the object with `output$___`. * Use a `render___({})` function to create the object. + Look at cheatsheet for examples. * Access inputs with `input$___`. UI-side: * Place output with `___Output()`. --- ## Next Steps **Troubleshooting common issues:** * Must comma separate all the elements in the `ui`. * But don't add a comma after the last element in the `ui`. **Up Next on Wednesday**: * Uploading apps to shinyapps.io * Updating `leaflet` maps * Displaying the code in your app * Modifying the layout and adding HTML components * Dashboards with `flexdashboard` * More practice