diff --git a/new_pages/shiny_basics.qmd b/new_pages/shiny_basics.qmd index d46e81b..dd1b782 100644 --- a/new_pages/shiny_basics.qmd +++ b/new_pages/shiny_basics.qmd @@ -56,21 +56,25 @@ In this page, we will use the first approach of having one file called *app.R*. library(shiny) ui <- fluidPage( - - # Application title - titlePanel("My app"), - - # Sidebar with a slider input widget - sidebarLayout( - sidebarPanel( - sliderInput("input_1") - ), - - # Show a plot - mainPanel( - plotOutput("my_plot") - ) - ) + + # Application title + titlePanel("My app"), + + # Sidebar with a slider input widget + sidebarLayout( + sidebarPanel( + sliderInput(inputId = "input_1", + label = "Slider", + min = 1, + max = 50, + value = 1) + ), + + # Show a plot + mainPanel( + plotOutput("my_plot") + ) + ) ) # Define server logic required to draw a histogram @@ -103,11 +107,11 @@ We next need to understand what the `server` and `ui` objects actually _do_. *Pu The UI element of a shiny app is, on a basic level, R code that creates an HTML interface. This means everything that is *displayed* in the UI of an app. This generally includes: -* "Widgets" - dropdown menus, check boxes, sliders, etc that can be interacted with by the user -* Plots, tables, etc - outputs that are generated with R code +* "Widgets" - dropdown menus, check boxes, sliders, etc that can be interacted with by the user. +* Plots, tables, etc - outputs that are generated with R code. * Navigation aspects of an app - tabs, panes, etc. -* Generic text, hyperlinks, etc -* HTML and CSS elements (addressed later) +* Generic text, hyperlinks, etc. +* HTML and CSS elements (addressed later). The most important thing to understand about the UI is that it *receives inputs* from the user and *displays outputs* from the server. There is no *active* code running in the ui *at any time* - all changes seen in the UI are passed through the server (more or less). So we have to make our plots, downloads, etc in the server @@ -120,9 +124,10 @@ This all probably sounds very abstract for now, so we'll have to dive into some Before you begin to build an app, its immensely helpful to know *what* you want to build. Since your UI will be written in code, you can't really visualise what you're building unless you are aiming for something specific. For this reason, it is immensely helpful to look at lots of examples of shiny apps to get an idea of what you can make - even better if you can look at the source code behind these apps! Some great resources for this are: -* The [Rstudio app gallery](https://shiny.rstudio.com/gallery/) +* [Rstudio app gallery](https://shiny.rstudio.com/gallery/) +* [Appsilon R Shiny Demo Gallery](https://www.appsilon.com/shiny-demo-gallery) -Once you get an idea for what is possible, it's also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It's helpful to start simple for your first app! There's also no shame in using code you find online of a nice app as a template for your work - its much easier than building something from scratch! +Once you get an idea for what is possible, it's also helpful to map out what you want yours to look like - you can do this on paper or in any drawing software (PowerPoint, MS paint, etc.). It's helpful to start simple for your first app! There's also no shame in using code you find online of a nice app as a template for your work - its much easier and more efficient than building something from scratch! @@ -130,17 +135,17 @@ Once you get an idea for what is possible, it's also helpful to map out what you When building our app, its easier to work on the UI first so we can see what we're making, and not risk the app failing because of any server errors. As mentioned previously, its often good to use a template when working on the UI. There are a number of standard layouts that can be used with shiny that are available from the base shiny package, but it's worth noting that there are also a number of package extensions such as `shinydashboard`. We'll use an example from base shiny to start with. -A shiny UI is generally defined as a series of nested functions, in the following order +A shiny UI is generally defined as a series of nested functions, in the following order: -1. A function defining the general layout (the most basic is `fluidPage()`, but more are available) +1. A function defining the general layout (the most basic is `fluidPage()`, but more are available). 2. Panels within the layout such as: - - a sidebar (`sidebarPanel()`) - - a "main" panel (`mainPanel()`) - - a tab (`tabPanel()`) - - a generic "column" (`column()`) -3. Widgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs) - - Widgets generally are styled as `xxxInput()` e.g. `selectInput()` - - Outputs are generally styled as `xxxOutput()` e.g. `plotOutput()` + - a sidebar (`sidebarPanel()`). + - a "main" panel (`mainPanel()`). + - a tab (`tabPanel()`). + - a generic "column" (`column()`). +3. Widgets and outputs - these can confer inputs to the server (widgets) or outputs from the server (outputs). + - Widgets generally are styled as `xxxInput()` e.g. `selectInput()`. + - Outputs are generally styled as `xxxOutput()` e.g. `plotOutput()`. It's worth stating again that these can't be visualised easily in an abstract way, so it's best to look at an example! Lets consider making a basic app that visualises our malaria facility count data by district. This data has a lot of differnet parameters, so it would be great if the end user could apply some filters to see the data by age group/district as they see fit! We can use a very simple shiny layout to start - the sidebar layout. This is a layout where widgets are placed in a sidebar on the left, and the plot is placed on the right. @@ -228,7 +233,7 @@ It's also worth noting the *named vector* we used for the age data here. For man There are loads of widgets that you can use to do lots of things with your app. Widgets also allow you to upload files into your app, and download outputs. There are also some excellent shiny extensions that give you access to more widgets than base shiny - the **shinyWidgets** package is a great example of this. To look at some examples you can look at the following links: - [base shiny widget gallery](https://shiny.rstudio.com/gallery/widget-gallery.html) -- [shinyWidgets gallery](https://github.com/dreamRs/shinyWidgets) +- [shinyWidgets gallery](https://dreamrs.github.io/shinyWidgets/) @@ -238,25 +243,26 @@ The next step in our app development is getting the server up and running. To do So given we want to make an app that shows epi curves that change based on user input, we should think about what code we would need to run this in a normal R script. We'll need to: -1. Load our packages -2. Load our data -3. Transform our data -4. Develop a _function_ to visualise our data based on user inputs +1. Load our packages. +2. Load our data. +3. Transform our data. +4. Develop a _function_ to visualise our data based on user inputs. This list is pretty straightforward, and shouldn't be too hard to do. It's now important to think about which parts of this process need to *be done only once* and which parts need to *run in response to user inputs*. This is because shiny apps generally run some code before running, which is only performed once. It will help our app's performance if as much of our code can be moved to this section. For this example, we only need to load our data/packages and do basic transformations once, so we can put that code *outside the server*. This means the only thing we'll need in the server is the code to visualise our data. Lets develop all of these componenets in a script first. However, since we're visualising our data with a function, we can also put the code _for the function_ outside the server so our function is in the environment when the app runs! First lets load our data. Since we're working with a new project, and we want to make it clean, we can create a new directory called data, and add our malaria data in there. We can run this code below in a testing script we will eventually delete when we clean up the structure of our app. ```{r, echo = TRUE} -pacman::p_load("tidyverse", "lubridate") +pacman::p_load( + "tidyverse", + "lubridate" + ) # read data -malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) %>% - as_tibble() +malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds")) print(malaria_data) - ``` @@ -267,7 +273,9 @@ It will be easier to work with this data if we use tidy data standards, so we sh malaria_data <- malaria_data %>% select(-newid) %>% - pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported") + pivot_longer(cols = starts_with("malaria_"), + names_to = "age_group", + values_to = "cases_reported") print(malaria_data) @@ -277,9 +285,9 @@ And with that we've finished preparing our data! This crosses items 1, 2, and 3 When defining our function, it might be hard to think about what parameters we want to include. For functional programming with shiny, every relevent parameter will generally have a widget associated with it, so thinking about this is usually quite easy! For example in our current app, we want to be able to filter by district, and have a widget for this, so we can add a district parameter to reflect this. We *don't* have any app functionality to filter by facility (for now), so we don't need to add this as a parameter. Lets start by making a function with three parameters: -1. The core dataset -2. The district of choice -3. The age group of choice +1. The core dataset. +2. The district of choice. +3. The age group of choice. ```{r} @@ -320,8 +328,13 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { } - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred" + ) + theme_minimal() + labs( x = "date", @@ -345,13 +358,15 @@ Let's test our function! ```{r, echo = TRUE, warning = FALSE} -plot_epicurve(malaria_data, district = "Bolo", agegroup = "malaria_rdt_0-4") +plot_epicurve(malaria_data, + district = "Bolo", + agegroup = "malaria_rdt_0-4") ``` With our function working, we now have to understand how this all is going to fit into our shiny app. We mentioned the concept of _startup code_ before, but lets look at how we can actually incorporate this into the structure of our app. There are two ways we can do this! -1. Put this code in your _app.R_ file at the start of the script (above the UI), or +1. Put this code in your _app.R_ file at the start of the script (above the UI), or, 2. Create a new file in your app's directory called _global.R_, and put the startup code in this file. It's worth noting at this point that it's generally easier, especially with bigger apps, to use the second file structure, as it lets you separate your file structure in a simple way. Lets fully develop a this global.R script now. Here is what it could look like: @@ -369,7 +384,9 @@ malaria_data <- rio::import(here::here("data", "malaria_facility_count_data.rds" # clean data and pivot longer malaria_data <- malaria_data %>% select(-newid) %>% - pivot_longer(cols = starts_with("malaria_"), names_to = "age_group", values_to = "cases_reported") + pivot_longer(cols = starts_with("malaria_"), + names_to = "age_group", + values_to = "cases_reported") # define plotting function @@ -411,9 +428,12 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { agegroup_title <- stringr::str_glue("{str_remove(agegroup, 'malaria_rdt')} years") } - - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred") + theme_minimal() + labs( x = "date", @@ -421,9 +441,7 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot") { title = stringr::str_glue("Malaria cases - {plot_title_district}"), subtitle = agegroup_title ) - - - + } @@ -445,7 +463,7 @@ Note that you should *always* specify `local = TRUE` in shiny apps, since it wil ## Developing an app server -Now that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It's important to understand that these functions are not executed in a linear order. There is an order to them, but it's not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, *unless the developer has set them up so they behave differently*. Again, this is all quite abstract, but lets first go through the three basic types of shiny _objects_ +Now that we have most of our code, we just have to develop our server. This is the final piece of our app, and is probably the hardest to understand. The server is a large R function, but its helpful to think of it as a series of smaller functions, or tasks that the app can perform. It's important to understand that these functions are not executed in a linear order. There is an order to them, but it's not fully necessary to understand when starting out with shiny. At a very basic level, these tasks or functions will activate when there is a change in user inputs that affects them, *unless the developer has set them up so they behave differently*. Again, this is all quite abstract, but lets first go through the three basic types of shiny _objects_: 1. Reactive sources - this is another term for user inputs. The shiny server has access to the outputs from the UI through the widgets we've programmed. Every time the values for these are changed, this is passed down to the server. @@ -508,10 +526,10 @@ ui <- fluidPage( From this code UI we have: - Two inputs: - - District selector (with an inputId of `select_district`) - - Age group selector (with an inputId of `select_agegroup`) + - District selector (with an inputId of `select_district`). + - Age group selector (with an inputId of `select_agegroup`). - One output: - - The epicurve (with an outputId of `malaria_epicurve`) + - The epicurve (with an outputId of `malaria_epicurve`). As stated previously, these unique names we have assigned to our inputs and outputs are crucial. They *must be unique* and are used to pass information between the ui and server. In our server, we access our inputs via the syntax `input$inputID` and outputs and passed to the ui through the syntax `output$output_name` Lets have a look at an example, because again this is hard to understand otherwise! @@ -520,7 +538,9 @@ As stated previously, these unique names we have assigned to our inputs and outp server <- function(input, output, session) { output$malaria_epicurve <- renderPlot( - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) ) } @@ -533,7 +553,7 @@ The server for a simple app like this is actually quite straightforward! You'll To understand the basics of how the server reacts to user inputs, you should note that the output will know (through the underlying package) when inputs change, and rerun this function to create a plot every time they change. Note that we also use the `renderPlot()` function here - this is one of a family of class-specific functions that pass those objects to a ui output. There are a number of functions that behave similarly, but you need to ensure the function used matches the class of object you're passing to the ui! For example: -- `renderText()` - send text to the ui +- `renderText()` - send text to the ui. - `renderDataTable` - send an interactive table to the ui. Remember that these also need to match the output *function* used in the ui - so `renderPlot()` is paired with `plotOutput()`, and `renderText()` is matched with `textOutput()`. @@ -561,9 +581,9 @@ knitr::include_graphics(here::here("images", "shiny", "listening.png")) At this point we've finally got a running app, but we have very little functionality. We also haven't really scratched the surface of what shiny can do, so there's a lot more to learn about! Lets continue to build our existing app by adding some extra features. Some things that could be nice to add could be: -1. Some explanatory text -2. A download button for our plot - this would provide the user with a high quality version of the image that they're generating in the app -3. A selector for specific facilities +1. Some explanatory text. +2. A download button for our plot - this would provide the user with a high quality version of the image that they're generating in the app. +3. A selector for specific facilities. 4. Another dashboard page - this could show a table of our data. This is a lot to add, but we can use it to learn about a bunch of different shiny featues on the way. There is so much to learn about shiny (it can get *very* advanced, but its hopefully the case that once users have a better idea of how to use it they can become more comfortable using external learning sources as well). @@ -776,7 +796,9 @@ Now that we have our ui ready, we need to add the server component. Downloads ar server <- function(input, output, session) { output$malaria_epicurve <- renderPlot( - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) ) output$download_epicurve <- downloadHandler( @@ -786,7 +808,9 @@ server <- function(input, output, session) { content = function(file) { ggsave(file, - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup), + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup), width = 8, height = 5, dpi = 300) } @@ -802,7 +826,7 @@ Note that the `content` function always takes a `file` argument, which we put wh Reactive conductors are objects that are created in the shiny server in a *reactive* way, but are not outputted - they can just be used by other parts of the server. There are a number of different kinds of *reactive conductors*, but we'll go through the basic two. -1.`reactive()` - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets) +1.`reactive()` - this is the most basic reactive conductor - it will react whenever any inputs used inside of it change (so our district/age group widgets). 2. `eventReactive()`- this rective conductor works the same as `reactive()`, except that the user can specify which inputs cause it to rerun. This is useful if your reactive conductor takes a long time to process, but this will be explained more later. Lets look at the two examples: @@ -811,7 +835,9 @@ Lets look at the two examples: malaria_plot_r <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -819,7 +845,9 @@ malaria_plot_r <- reactive({ # only runs when the district selector changes! malaria_plot_er <- eventReactive(input$select_district, { - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -837,7 +865,9 @@ Lets look at how we can integrate this into our server code: server <- function(input, output, session) { malaria_plot <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup) }) @@ -936,8 +966,12 @@ plot_epicurve <- function(data, district = "All", agegroup = "malaria_tot", faci - ggplot(data, aes(x = data_date, y = cases_reported)) + - geom_col(width = 1, fill = "darkred") + + ggplot(data, + mapping = aes(x = data_date, + y = cases_reported) + ) + + geom_col(width = 1, + fill = "darkred") + theme_minimal() + labs( x = "date", @@ -955,7 +989,10 @@ Let's test it: ```{r, warning=F, message=F} -plot_epicurve(malaria_data, district = "Spring", agegroup = "malaria_rdt_0-4", facility = "Facility 1") +plot_epicurve(malaria_data, + district = "Spring", + agegroup = "malaria_rdt_0-4", + facility = "Facility 1") ``` @@ -1068,17 +1105,17 @@ Notice how we're now passing variables for our choices instead of hard coding th The functions we need to understand how to do this are known as *observer* functions, and are similar to *reactive* functions in how they behave. They have one key difference though: -- Reactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server -- Observer functions *can* affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice) +- Reactive functions do not directly affect outputs, and produce objects that can be seen in other locations in the server. +- Observer functions *can* affect server outputs, but do so via side effects of other functions. (They can also do other things, but this is their main function in practice). Similar to reactive functions, there are two flavours of observer functions, and they are divided by the same logic that divides reactive functions: -1. `observe()` - this function runs whenever any inputs used inside of it change -2. `observeEvent()` - this function runs when a *user-specified* input changes +1. `observe()` - this function runs whenever any inputs used inside of it change. +2. `observeEvent()` - this function runs when a *user-specified* input changes. We also need to understand the shiny-provided functions that update widgets. These are fairly straightforward to run - they first take the `session` object from the server function (this doesn't need to be understood for now), and then the `inputId` of the function to be changed. We then pass new versions of all parameters that are already taken by `selectInput()` - these will be automatically updated in the widget. -Lets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to *only reflect those that are available in that district* (and an option for all facilities) +Lets look at an isolated example of how we could use this in our server. When the user changes the district, we want to filter our tibble of facilities by district, and update the choices to *only reflect those that are available in that district* (and an option for all facilities). ```{r, eval = FALSE} @@ -1094,7 +1131,8 @@ observe({ new_choices <- c("All", new_choices) - updateSelectInput(session, inputId = "select_facility", + updateSelectInput(session, + inputId = "select_facility", choices = new_choices) }) @@ -1108,7 +1146,10 @@ And that's it! we can add it into our server, and that behaviour will now work. server <- function(input, output, session) { malaria_plot <- reactive({ - plot_epicurve(malaria_data, district = input$select_district, agegroup = input$select_agegroup, facility = input$select_facility) + plot_epicurve(malaria_data, + district = input$select_district, + agegroup = input$select_agegroup, + facility = input$select_facility) }) @@ -1125,7 +1166,8 @@ server <- function(input, output, session) { new_choices <- c("All", new_choices) - updateSelectInput(session, inputId = "select_facility", + updateSelectInput(session, + inputId = "select_facility", choices = new_choices) }) @@ -1279,7 +1321,7 @@ ui <- fluidPage( ``` -Now our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via DT::renderDT() to the ui! +Now our app is arranged into tabs! Lets make the necessary edits to the server as well. Since we dont need to manipulate our dataset at all before we render it this is actually very simple - we just render the malaria_data dataset via `DT::renderDT()` to the ui! ```{r, eval = FALSE} @@ -1360,19 +1402,10 @@ For the purposes of this document, we will use _shinyapps.io_, since it is easie First we should make sure our app is suitable for publishing on a server. In your app, you should restart your R session, and ensure that it runs without running any extra code. This is important, as an app that requires package loading, or data reading not defined in your app code won't run on a server. Also note that you can't have any *explicit* file paths in your app - these will be invalid in the server setting - using the `here` package solves this issue very well. Finally, if you're reading data from a source that requires user-authentication, such as your organisation's servers, this will not generally work on a server. You will need to liase with your IT department to figure out how to whitelist the shiny server [here](https://docs.posit.co/shiny-server/). -*signing up for account* - Once you have your account, you can navigate to the tokens page under _Accounts_. Here you will want to add a new token - this will be used to deploy your app. From here, you should note that the url of your account will reflect the name of your app - so if your app is called _my_app_, the url will be appended as _xxx.io/my_app/_. Choose your app name wisely! Now that you are all ready, click deploy - if successful this will run your app on the web url you chose! -*something on making apps in documents?* - -## Further reading - -So far, we've covered a lot of aspects of shiny, and have barely scratched the surface of what is on offer for shiny. While this guide serves as an introduction, there is loads more to learn to fully understand shiny. You should start making apps and gradually add more and more functionality - - ## Recommended extension packages The following represents a selection of high quality shiny extensions that can help you get a lot more out of shiny. In no particular order: @@ -1397,5 +1430,9 @@ There are also a number of packages that can be used to create interactive outpu ## Recommended resources +So far, we've covered a lot of aspects of shiny, and have barely scratched the surface of what is on offer for shiny. While this guide serves as an introduction, there is loads more to learn to fully understand shiny. You should start making apps and gradually add more and more functionality. +[Welcome to Shiny (Tutorial)](https://shiny.posit.co/r/getstarted/shiny-basics/lesson1/index.html) +[Introducing Shiny (Tutorial)](http://wch.github.io/shiny/tutorial/) +[A Gentle Introduction to Shiny (Bookdown chapter)](https://bookdown.org/pdr_higgins/rmrwr/a-gentle-introduction-to-shiny.html) diff --git a/new_pages/time_series.qmd b/new_pages/time_series.qmd index efcf75b..b1f32f4 100644 --- a/new_pages/time_series.qmd +++ b/new_pages/time_series.qmd @@ -33,22 +33,23 @@ Topics covered include: This code chunk shows the loading of packages required for the analyses. In this handbook we emphasize `p_load()` from **pacman**, which installs the package if necessary *and* loads it for use. You can also load packages with `library()` from **base** R. See the page on [R basics](basics.qmd) for more information on R packages. ```{r load_packages} -pacman::p_load(rio, # File import - here, # File locator - tsibble, # handle time series datasets - slider, # for calculating moving averages - imputeTS, # for filling in missing values - feasts, # for time series decomposition and autocorrelation - forecast, # fit sin and cosin terms to data (note: must load after feasts) - trending, # fit and assess models - tmaptools, # for getting geocoordinates (lon/lat) based on place names - ecmwfr, # for interacting with copernicus sateliate CDS API - stars, # for reading in .nc (climate data) files - units, # for defining units of measurement (climate data) - yardstick, # for looking at model accuracy - surveillance, # for aberration detection - tidyverse # data management + ggplot2 graphics - ) +pacman::p_load( + rio, # File import + here, # File locator + tsibble, # handle time series datasets + slider, # for calculating moving averages + imputeTS, # for filling in missing values + feasts, # for time series decomposition and autocorrelation + forecast, # fit sin and cosin terms to data (note: must load after feasts) + trending, # fit and assess models + tmaptools, # for getting geocoordinates (lon/lat) based on place names + ecmwfr, # for interacting with copernicus sateliate CDS API + stars, # for reading in .nc (climate data) files + units, # for defining units of measurement (climate data) + yardstick, # for looking at model accuracy + surveillance, # for aberration detection + tidyverse # data management + ggplot2 graphics + ) ``` ### Load data {.unnumbered} @@ -1555,10 +1556,8 @@ observed <- predict(fitted_model, simulate_pi = FALSE) ```{r table_regression, eval = FALSE, echo = TRUE} -## show estimates and percentage change in a table -model %>% - ## extract original negative binomial regression - get_model() %>% +## extract original negative binomial regression +fitted_model$result[[1]] %>% ## get a tidy dataframe of results tidy(exponentiate = TRUE, conf.int = TRUE) %>%