diff --git a/01-overview.qmd b/01-overview.qmd index 39b0e34..6598efd 100644 --- a/01-overview.qmd +++ b/01-overview.qmd @@ -40,9 +40,9 @@ By the end of the module, students should be able to: To reproduce the code in the book, you need the following software packages: -- R-4.2.2 -- RStudio 2022.12.0-353 -- Quarto 1.2.280 +- R-4.3.1 +- RStudio 2023.09.0+463 +- Quarto 1.3.450 - the list of libraries in the next section To check your version of: @@ -64,6 +64,7 @@ The list of libraries used in this book is provided below: - `arm` - `car` - `corrplot` +- `devtools` - `FRK` - `gghighlight` - `ggplot2` @@ -94,15 +95,19 @@ The list of libraries used in this book is provided below: - `tmap` - `tufte` - `viridis` +- `basemapR` Copy, paste and run the code below in your console. Ensure all packages are installed on your computer. ```{r} #| eval: false -deps <- list( + +# package names +packages <- c( "arm", "car", "corrplot", + "devtools", "FRK", "gghighlight", "ggplot2", @@ -120,7 +125,6 @@ deps <- list( "merTools", "plyr", "RColorBrewer", - "rgdal", "sf", "sjPlot", "sp", @@ -133,14 +137,22 @@ deps <- list( "tufte", "viridis" ) -``` -```{r} -#| eval: false -# we can load them all to make sure they are installed: -for(lib in deps){library(lib, character.only = TRUE)} +# install packages not yet installed +installed_packages <- packages %in% rownames(installed.packages()) +if (any(installed_packages == FALSE)) { + install.packages(packages[!installed_packages]) +} + +# packages loading +invisible(lapply(packages, library, character.only = TRUE)) ``` +::: column-margin ::: callout-note To install the library `basemapR`, you need to install from source by running: + +`library(devtools)`\ +`install_github('Chrisjb/basemapR')` ::: ::: column-margin + ## Assessment The final module mark is composed of the *two computational essays*. Together they are designed to cover the materials introduced in the entirety of content covered during the semester. A computational essay is an essay whose narrative is supported by code and computational results that are included in the essay itself. Each teaching week, you will be required to address a set of questions relating to the module content covered in that week, and to use the material that you will produce for this purpose to build your computational essay. diff --git a/02-spatial_data.qmd b/02-spatial_data.qmd index bb367ac..ec3398e 100644 --- a/02-spatial_data.qmd +++ b/02-spatial_data.qmd @@ -53,7 +53,7 @@ An alternative approach is to use the smallest geographical system available and ### Ecological Fallacy -Ecological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is @robinson1950ecological who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. @robinson1950ecological used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was `0.53`. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative `-0.11` which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data. +Ecological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is @robinson1950ecological who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. @robinson1950ecological used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was 0.53. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative -0.11 which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data. ### Spatial Dependence diff --git a/03-data-wrangling.qmd b/03-data-wrangling.qmd index a6766b5..3cdcd9d 100644 --- a/03-data-wrangling.qmd +++ b/03-data-wrangling.qmd @@ -1,10 +1,8 @@ # Data Wrangling {#sec-chp3} -This chapter[^03-data-wrangling-1] introduces computational notebooks, basic functions and data types. These are all important concepts that we will use during the module. +In this chapter, we will cover the fundamentals of the concepts and functions that you will need to know to navigate this book. We will introduce key concepts and functions relating to what computational notebooks are and how they work. We will also cover basic R functions and data types, including the use of factors. Additionally, we will offer a basic understanding of the manipulation and mapping of spatial data frames using commonly used libraries such as `tidyverse`, `sf`, `ggplot` and `tmap`. -[^03-data-wrangling-1]: This chapter is part of [Spatial Analysis Notes](index.html) Creative Commons License
[Introduction -- R Notebooks + Basic Functions + Data Types]{xmlns:dct="http://purl.org/dc/terms/" property="dct:title"} by Francisco Rowe is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. - -If you are already familiar with R, R notebooks and data types, you may want to jump to Section [Read Data](#sec_readdata) and start from there. This section describes how to read and manipulate data using `sf` and `tidyverse` functions, including `mutate()`, `%>%` (known as pipe operator), `select()`, `filter()` and specific packages and functions how to manipulate spatial data. +If you are already familiar with R, R computational notebooks and data types, you may want to jump to Section [Read Data](#sec_readdata) and start from there. This section describes how to read and manipulate data using `sf` and `tidyverse` functions, including `mutate()`, `%>%` (known as pipe operator), `select()`, `filter()` and specific packages and functions how to manipulate spatial data. The chapter is based on: @@ -18,30 +16,33 @@ The chapter is based on: ## Dependencies -This tutorial uses the libraries below. Ensure they are installed on your machine[^03-data-wrangling-2] before loading them executing the following code chunk: +This chapter uses the libraries below. Ensure they are installed on your machine[^03-data-wrangling-1] before you progress. + +[^03-data-wrangling-1]: You can install package `mypackage` by running the command `install.packages("mypackage")` on the R prompt or through the `Tools --> Install Packages...` menu in RStudio. -[^03-data-wrangling-2]: You can install package `mypackage` by running the command `install.packages("mypackage")` on the R prompt or through the `Tools --> Install Packages...` menu in RStudio. +```{r} +#| include = FALSE +rm(list=ls()) +``` -```{r, message = FALSE} -# Data manipulation, transformation and visualisation +```{r} +#| warning = FALSE +# data manipulation, transformation and visualisation library(tidyverse) -# Nice tables +# nice tables library(kableExtra) -# Simple features (a standardised way to encode vector data ie. points, lines, polygons) +# spatial data manipulation library(sf) -# Spatial objects conversion -library(sp) -# Thematic maps +# thematic mapping library(tmap) -# Colour palettes +# colour palettes library(RColorBrewer) -# More colour palettes library(viridis) ``` ## Introducing R -R is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on [CRAN](https://cran.r-project.org). CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R. +R is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on [The Comprehensive R Archive Network (CRAN)](https://cran.r-project.org). CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R. Commands are sent to R using either the terminal / command line or the R Console which is installed with R on either Windows or OS X. On Linux, there is no equivalent of the console, however, third party solutions exist. On your own machine, R can be installed from [here](https://www.r-project.org/). @@ -58,11 +59,16 @@ If you would like to know more about the various features of RStudio, watch this Before we start any analysis, ensure to set the path to the directory where we are working. We can easily do that with `setwd()`. Please replace in the following line the path to the folder where you have placed this file -and where the `data` folder lives. ```{r} -#setwd('../data/sar.csv') -#setwd('.') +#| eval: false +setwd('../data/sar.csv') +setwd('.') ``` -Note: It is good practice to not include spaces when naming folders and files. Use *underscores* or *dots*. +::: column-margin +::: callout-note +It is good practice to not include spaces when naming folders and files. Use *underscores* or *dots*. +::: +::: You can check your current working directory by typing: @@ -74,6 +80,14 @@ getwd() An *R script* is a series of commands that you can execute at one time and help you save time. So you do not repeat the same steps every time you want to execute the same process with different datasets. An R script is just a plain text file with R commands in it. +::: column-margin +::: callout-note +To get familiar with good practices in writing your code in R, we recommend the [Chapter Workflow: basics](https://r4ds.hadley.nz/workflow-basics.html) and [Workflow: scripts and projects](https://r4ds.hadley.nz/workflow-scripts) from the R in Data Science book by @wickham2023r. +::: +::: + +https://r4ds.hadley.nz/workflow-basics.html + To create an R script in RStudio, you need to - Open a new script file: *File* \> *New File* \> *R Script* @@ -88,7 +102,7 @@ mtcars - Save the script: *File* \> *Save As*, select your required destination folder, and enter any filename that you like, provided that it ends with the file extension *.R* -An *R Notebook* is an R Markdown document with descriptive text and code chunks that can be executed independently and interactively, with output visible immediately beneath a code chunk - see @Xie_et_al_2019_book. +An *R Notebook* or a *Quarto Document* are a Markdown options with descriptive text and code chunks that can be executed independently and interactively, with output visible immediately beneath a code chunk - see @Xie_et_al_2019_book. A *Quarto Document* is an improved version of the original *R Notebook*. *Quarto Document* requires a package called [Quarto](https://quarto.org). Quarto does not have a dependency or requirement for R. Quarto is multilingual, beginning with R, Python, Javascript, and Julia. The concept is that Quarto will work even for languages that do not yet exist. This book was original written in *R Notebook* but later transitioned into *Quarto Documents*. To create an R Notebook, you need to: @@ -102,7 +116,7 @@ To create an R Notebook, you need to: 2) use the keyboard shortcut *Ctrl + Alt + I* or *Cmd + Option + I* (Mac); or, 3) type the chunk delimiters ```` ```{r} ```` and ```` ``` ```` -In a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets eg. +In a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets e.g.: ```{r, include=FALSE} hist(mtcars$mpg) @@ -116,9 +130,7 @@ hist(mtcars$mpg) Rstudio also offers a *Preview* option on the toolbar which can be used to create pdf, html and word versions of the notebook. To do this, choose from the drop-down list menu `knit to ...` -For this module, we will be using computational notebooks through [Quarto](https://quarto.org); that is, *Quarto Document*. *"Quarto is a multi-language, next generation version of R Markdown from RStudio, with many new new features and capabilities. Like R Markdown, Quarto uses Knitr to execute R code, and is therefore able to render most existing Rmd files without modification."* - -To create a Quarto Document, you need to: +To create a *Quarto Document*, you need to: - Open a new script file: *File* \> *New File* \> *Quarto Document* @@ -128,13 +140,15 @@ Quarto Documents work in the same way as R Notebooks with small variations. You You can use `help` or `?` to ask for details for a specific function: -```{r, eval=FALSE} -help(sqrt) #or ?sqrt +```{r} +#| eval: false +help(sqrt) #or +?sqrt ``` And using `example` provides examples for said function: -```{r, fig.margin = TRUE, fig.cap = 'Example sqrt'} +```{r} example(sqrt) ``` @@ -236,7 +250,7 @@ In statistics, we differentiate between data to capture: In R these three types of random variables are represented by the following types of R data object: -```{r, echo=FALSE, fig.fullwidth = FALSE, fig.margin = FALSE, fig.cap= 'Survey and R data types'} +```{r, echo=FALSE} text_tbl <- data.frame( variables = c("nominal", "ordinal", "discrete", "continuous"), @@ -252,9 +266,9 @@ We have already encountered the R data type *numeric*. The next section introduc **What is a factor?** -A factor variable assigns a numeric code to each possible category (*level*) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of `10,000` *males* and *females* to a list of `10,000` `1s` and `0s`. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes. +A factor variable assigns a numeric code to each possible category (*level*) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of 10,000 *males* and *females* to a list of 10,000 1s and 0s. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes. -For example, the variable *gender*, converted to a factor, would be stored as a series of `1s` and `2s`, where `1 = female` and `2 = male`; but would be displayed in all outputs using their category labels of *female* and *male*. +For example, the variable *gender*, converted to a factor, would be stored as a series of 1s and 2s, where 1 = female and 2 = male; but would be displayed in all outputs using their category labels of *female* and *male*. **Creating a factor** @@ -268,13 +282,13 @@ class(gender) str(gender) ``` -Now *gender* is a factor and is stored as a series of `1s` and `2s`, with `1s` representing `females` and `2s` representing `males`. The function `levels( )` lists the levels (categories) associated with a given factor variable: +Now *gender* is a factor and is stored as a series of 1s and 2s, with 1s representing females and 2s representing males. The function `levels( )` lists the levels (categories) associated with a given factor variable: ```{r} levels(gender) ``` -The categories are reported in the order that they have been numbered (starting from `1`). Hence from the output we can infer that `females` are coded as `1`, and `males` as `2`. +The categories are reported in the order that they have been numbered (starting from 1). Hence from the output we can infer that females are coded as 1, and males as 2. ## Data Frames @@ -309,13 +323,15 @@ str(df) # or use glimpse(data) ### Referencing Data Frames -Throughout this module, you will need to refer to particular parts of a dataframe - perhaps a particular column (an area attribute); or a particular subset of respondents. Hence it is worth spending some time now mastering this particular skill. +To refer to particular parts of a dataframe - say, a particular column (an area attribute), or a subset of respondents. Hence it is worth spending some time understanding how to reference dataframes. The relevant R function, `[ ]`, has the format `[row,col]` or, more generally, `[set of rows, set of cols]`. Run the following commands to get a feel of how to extract different slices of the data: -```{r, eval=FALSE} +```{r} +#| eval: false + df # whole data.frame df[1, 1] # contents of first row and column df[2, 2:3] # contents of the second row, second and third columns @@ -337,7 +353,8 @@ Run both of these fuctions on their own to get a better understanding of what th Three other methods for referencing the contents of a data.frame make direct use of the variable names within the data.frame, which tends to make for easier to read/understand code: -```{r, eval=FALSE} +```{r} +#| eval: false df[,"pop"] # variable name in quotes inside the square brackets df$pop # variable name prefixed with $ and appended to the data.frame name # or you can use attach @@ -359,6 +376,12 @@ Ensure your memory is clear rm(list=ls()) # rm for targeted deletion / ls for listing all existing objects ``` +::: column-margin +::: callout-note +When opening a file, ensure the correct directory set up pointing to your data. It may differ from your existing working directory. +::: +::: + There are many commands to read / load data onto R. The command to use will depend upon the format they have been saved. Normally they are saved in *csv* format from Excel or other software packages. So we use either: - `df <- read.table("path/file_name.csv", header = FALSE, sep =",")` @@ -370,19 +393,16 @@ To read files in other formats, refer to this useful [DataCamp tutorial](https:/ ```{r} census <- read.csv("data/census/census_data.csv") head(census) -# NOTE: always ensure your are setting the correct directory leading to the data. -# It may differ from your existing working directory + ``` ### Quickly inspect the data -1. What class? - -2. What R data types? +Using the following questions to lead the inspection: What class? What R data types? What data types? -3. What data types? +```{r} +#| eval: false -```{r, eval=FALSE} # 1 class(census) # 2 & 3 @@ -406,7 +426,8 @@ or want to view the data: Usually you want to add / create new variables to your data frame using existing variables eg. computing percentages by dividing two variables. There are many ways in which you can do this i.e. referecing a data frame as we have done above, or using `$` (e.g. `census$pop`). For this module, we'll use `tidyverse`: ```{r} -census <- census %>% mutate(per_ghealth = ghealth / pop) +census <- census %>% + mutate( per_ghealth = ghealth / pop ) ``` Note we used a *pipe operator* `%>%`, which helps make the code more efficient and readable - more details, see @grolemund_wickham_2019_book. When using the pipe operator, recall to first indicate the data frame before `%>%`. @@ -418,7 +439,8 @@ Note also the use a variable name before the `=` sign in brackets to indicate th Usually you want to select a subset of variables for your analysis as storing to large data sets in your R memory can reduce the processing speed of your machine. A selection of data can be achieved by using the `select` function: ```{r} -ndf <- census %>% select(ward, pop16_74, per_ghealth) +ndf <- census %>% + select( ward, pop16_74, per_ghealth ) ``` Again first indicate the data frame and then the variable you want to select to build a new data frame. Note the code chunk above has created a new data frame called `ndf`. Explore it. @@ -428,14 +450,15 @@ Again first indicate the data frame and then the variable you want to select to You may also want to filter values based on defined conditions. You may want to filter observations greater than a certain threshold or only areas within a certain region. For example, you may want to select areas with a percentage of good health population over 50%: ```{r} -ndf2 <- census %>% filter(per_ghealth < 0.5) +ndf2 <- census %>% + filter( per_ghealth < 0.5 ) ``` You can use more than one variables to set conditions. Use "`,`" to add a condition. ### Joining Data Drames -When working with spatial data, we often need to join data. To this end, you need a common unique `id variable`. Let's say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (ie. `census_data2.csv`). +When working with spatial data, we often need to join data. To this end, you need a common unique `id variable`. Let's say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (i.e. `census_data2.csv`). ```{r} # read data @@ -448,38 +471,48 @@ The variable `geo_code` in this data frame corresponds to the `code` in the exis ```{r} # join data frames -join_dfs <- merge(census, census2, by.x="code", by.y="geo_code", all.x = TRUE) +join_dfs <- merge( census, # df1 + census2, # df2 + by.x="code", by.y="geo_code", # common ids + all.x = TRUE) # check data head(join_dfs) ``` ### Saving Data -It may also be convinient to save your R projects. They contains all the objects that you have created in your workspace by using the `save.image( )` function: +It may also be convenient to save your R projects. They contains all the objects that you have created in your workspace by using the `save.image( )` function: -```{r, eval=FALSE} +```{r} +#| eval: false save.image("week1_envs453.RData") ``` This creates a file labelled "week1_envs453.RData" in your working directory. You can load this at a later stage using the `load( )` function. -```{r,eval=FALSE} +```{r} +#| eval: false load("week1_envs453.RData") ``` Alternatively you can save / export your data into a `csv` file. The first argument in the function is the object name, and the second: the name of the csv we want to create. -```{r, eval=FALSE} +```{r} +#| eval: false write.csv(join_dfs, "join_censusdfs.csv") ``` ## Using Spatial Data Frames -A core area of this module is learning to work with spatial data in R. R has various purposedly designed **packages** for manipulation of spatial data and spatial analysis techniques. Various R packages exist in CRAN eg. `spatial`, `sgeostat`, `splancs`, `maptools`, `tmap`, `rgdal`, `spand` and more recent development of `sf` - see @lovelace2019 for a great description and historical context for some of these packages. +A core area of the module is learning to work with spatial data in R. R has various purposedly designed `packages` for manipulation of spatial data and spatial analysis techniques. Various packages exist in CRAN, including `sf` [@sf2018; @R-sf], `stars` [@R-stars], `terra`, `s2` [@R-s2], `lwgeom` [@R-lwgeom], `gstat` [@pebesma2004; @R-gstat], `spdep` [@R-spdep], `spatialreg` [@R-spatialreg], `spatstat` [@baddeley2015spatial; @R-spatstat], `tmap` [@tmap2018; @R-tmap], `mapview` [@R-mapview] and more. A key package is this ecosystem is `sf` [@pebesma2023spatial]. R package `sf` provides a table format for simple features, where feature geometries are stored in a list-column. It appeared in 2016 and was developed to move spatial data analysis in R closer to standards-based approaches seen in the industry and open source projects, to build upon more modern versions of open source geospatial software stack and allow for integration of R spatial software with the `tidyverse` [@tidyverse2019], particularly `ggplot2`, `dplyr`, and `tidyr`. Hence, this book relies heavely on `sf` for the manipulation and analysis of the data. -During this session, we will use `sf`. +::: column-margin +::: callout-note +@lovelace2024 provide a helpful overview and evolution of R spatial package ecosystem. +::: +::: -We first need to import our spatial data. We will use a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data. +To read our spatial data, we use the `st_read` function. We read a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data. ### Read Spatial Data @@ -489,7 +522,8 @@ oa_shp <- st_read("data/census/Liverpool_OA.shp") Examine the input data. A spatial data frame stores a range of attributes derived from a shapefile including the **geometry** of features (e.g. polygon shape and location), **attributes** for each feature (stored in the .dbf), [projection](https://en.wikipedia.org/wiki/Map_projection) and coordinates of the shapefile's bounding box - for details, execute: -```{r, eval=FALSE} +```{r} +#| eval: false ?st_read ``` @@ -504,15 +538,19 @@ str(oa_shp) head(oa_shp) ``` -**TASK:** +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -- What are the geographical hierarchy in these data? -- What is the smallest geography? -- What is the largest geography? +- What are the geographical hierarchy in these data?\ +- What is the smallest geography?\ +- What is the largest geography?\ +::: +::: ### Basic Mapping -Again, many functions exist in CRAN for creating maps: +Many functions exist in CRAN for creating maps: - `plot` to create static maps - `tmap` to create static and interactive maps @@ -521,28 +559,83 @@ Again, many functions exist in CRAN for creating maps: - `ggplot2` to create data visualisations, including static maps - `shiny` to create web applications, including maps -Here this notebook demonstrates the use of `plot` and `tmap`. First `plot` is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right, +In this book, we will make use of `plot`, `tmap` and `ggplot`. Normally you use `plot` to get a quick inspection of the data and `tmap` and `ggplot` to get publication quality data visualisations. First `plot` is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right. + +**Using `plot`** -#### Using `plot` +We can use the base `plot` function to display the boundaries of OAs in Liverpool. -```{r, fig.margin = TRUE, fig.cap = 'OAs of Livepool'} +```{r} # mapping geometry plot(st_geometry(oa_shp)) ``` -and then: +To visualise a column in the spatial data frame, we can run: -```{r, fig.cap = 'Spatial distribution of ethnic groups, Liverpool'} +```{r} # map attributes, adding intervals -plot(oa_shp["Ethnic"], key.pos = 4, axes = TRUE, key.width = lcm(1.3), key.length = 1., - breaks = "jenks", lwd = 0.1, border = 'grey') +plot(oa_shp["Ethnic"], # variable to visualise + key.pos = 4, + axes = TRUE, + key.width = lcm(1.3), + key.length = 1., + breaks = "jenks", # algorithm to categorise the data + lwd = 0.1, + border = 'grey') # boundary colour ``` -**TASK:** +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** + +What is the key pattern emerging from this map? +::: +::: -- What is the key pattern emerging from this map? +Let us now explore `ggplot` or `tmap`. -#### Using `tmap` +**Using `ggplot`** + +We can visualise spatial data frames using `ggplot` fairly easily. `ggplot` is a generic sets of functions which was not specifically designed for spatial mapping, but it is fairly flexible that allows producing great spatial data visualisations. + +Following the grammar of `ggplot`, we plot spatial data drawing layers. `ggplot` has a basic structure of three components: + +- The data i.e. `ggplot( data = *data frame*)`. +- Geometries i.e. `geom_xxx( )`. +- Aesthetic mapping i.e. `aes(x=*variable*, y=*variable*)` + +We can put these three components together using `+`. This is similar to the ways we apply the pipe operator in for tidyverse. The latter component of aesthetic mapping can be added in both the `ggplot` and `geom_xxx` functions. + +To map our data, we can then run: + +```{r} +ggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map + geom_sf(colour = "gray60", # colour line + size = 0.1) # line size +``` + +We can change the colour palette by using a different colour palette. We can use the [viridis](https://cran.r-project.org/web/packages/viridis/vignettes/intro-to-viridis.html) package. The power of `viridis` is that it uses color scales that visually pleasing, colorblind friendly, print well in gray scale, and can be used for both categorical and continuous data. For categorical data you can also use [ColourBrewer](https://colorbrewer2.org/#type=sequential&scheme=BuGn&n=3). + +So let us (1) change the colour pallete to viridis, (2) remove the colour of the boundaries, and (3) replace the theme with the theme we will be using for the book. Let's read the theme first and then implement these changes. + +The `ggplot` themes for the book are in a file called `data-visualisation_theme.R` in the `style` folder. We read this file by running: + +```{r} +source("./style/data-visualisation_theme.R") +``` + +We can now implement our changes: + +```{r} +ggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map + geom_sf(colour = "transparent") + # colour line + scale_fill_viridis( option = "viridis" ) + # add viridis colour scheme + theme_map_tufte() +``` + +To master `ggplot`, see @wickham2009. + +**Using `tmap`** Similar to `ggplot2`, `tmap` is based on the idea of a 'grammar of graphics' which involves a separation between the input data and aesthetics (i.e. the way data are visualised). Each data set can be mapped in various different ways, including location as defined by its geometry, colour and other features. The basic building block is `tm_shape()` (which defines input data), followed by one or more layer elements such as `tm_fill()` and `tm_dots()`. @@ -560,22 +653,34 @@ map_oa = tm_shape(oa_shp) + map_oa ``` -Note that the operation `+` is used to add new layers. You can set style themes by `tm_style`. To visualise the existing styles use `tmap_style_catalogue()`, and you can also evaluate the code chunk below if you would like to create an interactive map. +Note that the operation `+`, as for `ggplot` is used to add new layers. You can set style themes by `tm_style`. To visualise the existing styles use `tmap_style_catalogue()`. An advantage of `tmap` is that you can easily create an interactive map by running `tmap_mode`. -```{r, eval=FALSE} +```{r} tmap_mode("view") map_oa ``` -**TASK:** +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -- Try mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate? +Try mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate? +::: +::: ### Comparing geographies -If you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see lecture notes. To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together. +If you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see @spatial_data. To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together. + +::: column-margin +::: callout-note +The first line of the chunk code include `tmap_mode("plot")` which tells R that we want a static map. `tmap_mode` works like a switch to interactive and non-interactive mapping. +::: +::: ```{r} +tmap_mode("plot") + # read data at the msoa level msoa_shp <- st_read("data/census/Liverpool_MSOA.shp") @@ -593,23 +698,12 @@ map_msoa = tm_shape(msoa_shp) + tmap_arrange(map_msoa, map_oa) ``` -**TASK:** - -- What differences do you see between OAs and MSOAs? -- Can you identify areas of spatial clustering? Where are they? +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -## Useful Functions +What differences do you see between OAs and MSOAs? -| Function | Description | -|--------------------------------|----------------------------------------| -| read.csv() | read csv files into data frames | -| str() | inspect data structure | -| mutate() | create a new variable | -| filter() | filter observations based on variable values | -| %\>% | pipe operator - chain operations | -| select() | select variables | -| merge() | join dat frames | -| st_read | read spatial data (ie. shapefiles) | -| plot() | create a map based a spatial data set | -| tm_shape(), tm_fill(), tm_borders() | create a map using tmap functions | -| tm_arrange | display multiple maps in a single "metaplot" | +Can you identify areas of spatial clustering? Where are they? +::: +::: diff --git a/04-points.qmd b/04-points.qmd index 3f35fbc..e8166d1 100644 --- a/04-points.qmd +++ b/04-points.qmd @@ -10,23 +10,25 @@ This chapter is based on the following references, which are great follow-up's o We will rely on the following libraries in this section, all of them included in @sec-dependencies: -```{r results='hide', , warning=FALSE, message=FALSE} -# For pretty table -library(knitr) -# All things geodata +```{r} +#| include = FALSE +source("./style/data-visualisation_theme.R") +``` + +```{r} +#| warning = FALSE +# data manipulation, transformation and visualisation +library(tidyverse) +# spatial data manipulation library(sf) library(sp) -# Pretty graphics -library(ggplot2) +# data visualisation library(gridExtra) -# Thematic maps -library(tmap) -# Pretty maps -library(ggmap) -# For all your interpolation needs +# basemap +library(basemapR) +# interpolation library(gstat) -# For data manipulation -library(plyr) +library(hexbin) ``` Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with `setwd()`. Please replace in the following line the path to the folder where you have placed this file -and where the `house_transactions` folder with the data lives. @@ -51,21 +53,19 @@ The rest of this session will focus on two main elements of the table: the spati ```{r fig.margin=TRUE, fig.cap="Raw AirBnb prices in San Diego"} # Create the histogram -hist <- qplot(data=db,x=price) -hist +qplot( data = db, x = price) ``` This basically shows there is a lot of values concentrated around the lower end of the distribution but a few very large ones. A usual transformation to *shrink* these differences is to take logarithms. The original table already contains an additional column with the logarithm of each price (`log_price`). -```{r fig.margin=TRUE, fig.cap="Log of AirBnb price in San Diego"} +```{r} # Create the histogram -hist <- qplot(data=db, x=log_price) -hist +qplot( data = db, x = log_price ) ``` To obtain the spatial distribution of these houses, we need to focus on the `geometry` column. The easiest, quickest (and also "dirtiest") way to get a sense of what the data look like over space is using `plot`: -```{r fig.margin=TRUE, fig.cap="Spatial distribution of AirBnb in San Diego"} +```{r} plot(st_geometry(db)) ``` @@ -78,26 +78,29 @@ The two-dimensional sister of histograms are binning maps: we divide each of the ```{r} # Squared binning # Set up plot -sqbin <- ggplot() + +sqbin <- ggplot( ) + # Add 2D binning with the XY coordinates as # a dataframe geom_bin2d( - data=as.data.frame(st_coordinates(db)), - aes(x=X, y=Y) - ) + data = as.data.frame( st_coordinates( db ) ), + aes( x = X, y = Y) + ) + + # set theme + theme_plot_tufte() # Hex binning # Set up plot hexbin <- ggplot() + # Add hex binning with the XY coordinates as # a dataframe geom_hex( - data=as.data.frame(st_coordinates(db)), - aes(x=X, y=Y) + data = as.data.frame( st_coordinates( db ) ), + aes( x = X, y = Y) ) + # Use viridis for color encoding (recommended) - scale_fill_continuous(type = "viridis") + scale_fill_continuous( type = "viridis" ) + + theme_plot_tufte() # Bind in subplots -grid.arrange(sqbin, hexbin, ncol=2) +grid.arrange( sqbin, hexbin, ncol = 2 ) ``` ## KDE @@ -108,7 +111,7 @@ Kernel Density Estimation (KDE) is a technique that creates a *continuous* repre KDE over a single dimension is essentially a contiguous version of a histogram. We can see that by overlaying a KDE on top of the histogram of logs that we have created before: -```{r fig.fullwidth=TRUE, fig.cap="Histogram and KDE of the log of AirBnb prices in San Diego"} +```{r} # Create the base base <- ggplot(db, aes(x=log_price)) # Histogram @@ -116,7 +119,8 @@ hist <- base + geom_histogram(bins=50, aes(y=..density..)) # Overlay density plot kde <- hist + - geom_density(fill="#FF6666", alpha=0.5, colour="#FF6666") + geom_density(fill="#FF6666", alpha=0.5, colour="#FF6666") + + theme_plot_tufte() kde ``` @@ -128,44 +132,35 @@ Geography, at the end of the day, is usually represented as a two-dimensional sp To create a spatial KDE in R, we can use general tooling for non-spatial points, such as the `stat_density2d_filled` method: -```{r fig.margin=TRUE, fig.cap="KDE of AirBnb properties in San Diego"} +```{r} # Create the KDE surface kde <- ggplot(data = db) + - stat_density2d_filled(alpha = 0.5, + stat_density2d_filled(alpha = 1, data = as.data.frame(st_coordinates(db)), aes(x = X, y = Y), - n = 16 + n = 100 ) + # Tweak the color gradient scale_color_viridis_c() + # White theme - theme_bw() -# Tip! Add invisible points to improve proportions -kde + geom_sf(alpha=0) + theme_plot_tufte() +kde ``` -This approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library `ggmap`, which is designed to play nicely with the `ggplot2` family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though. - -```{r fig.fullwidth=TRUE, fig.cap="KDE of AirBnb properties in San Diego", warning=FALSE, message=FALSE} -# Reproject coordinates -lon_lat <- st_transform(db, crs = 4326) %>% - st_coordinates() %>% - as.data.frame() -# Basemap -qmplot( - X, - Y, - data = lon_lat, - geom="blank" -) + - # KDE - stat_density2d_filled(alpha = 0.5, - data = lon_lat, +This approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library `basemapR`, which is designed to play nicely with the `ggplot2` family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though. + +```{r} + +bbox_db <- st_bbox(db) +ggplot() + + base_map(bbox_db, increase_zoom = 2, basemap = "positron") + + #geom_sf(data = db, fill = NA) + + stat_density2d_filled(alpha = 0.7, + data = as.data.frame(st_coordinates(db)), aes(x = X, y = Y), - n = 16 - ) + - # Tweak the color gradient - scale_color_viridis_c() + n = 100 + ) + ``` ## Spatial Interpolation diff --git a/05-flows.qmd b/05-flows.qmd index 561361f..1158dff 100644 --- a/05-flows.qmd +++ b/05-flows.qmd @@ -13,7 +13,9 @@ Content is based on the following references, which are great follow-up's on the We will rely on the following libraries in this section, all of them included in @sec-dependencies: -```{r results='hide', message = FALSE, warning=FALSE} +```{r} +#| message: false +#| warning: false # Data management library(tidyverse) # Spatial Data management @@ -23,20 +25,14 @@ library(sp) library(ggplot2) # Thematic maps library(tmap) -# Pretty maps -library(ggmap) +# Add basemaps +library(basemapR) # Simulation methods library(arm) ``` In this chapter we will show a slightly different way of managing spatial data in R. Although most of the functionality will be similar to that seen in previous chapters, we will not rely on the "`sf` stack" and we will instead show how to read and manipulate data using the more traditional `sp` stack. Although this approach is being slowly phased out, it is still important to be aware of its existence and its differences with more modern approaches. -Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with `setwd()`. Please replace in the following line the path to the folder where you have placed this file -and where the `sf_bikes` folder with the data lives. - -```{r} -setwd('.') -``` - ## Data In this note, we will use data from the city of San Francisco representing bike trips on their public bike share system. The original source is the SF Open Data portal ([link](http://www.bayareabikeshare.com/open-data)) and the dataset comprises both the location of each station in the Bay Area as well as information on trips (station of origin to station of destination) undertaken in the system from September 2014 to August 2015 and the following year. Since this note is about modeling and not data preparation, a cleanly reshaped version of the data, together with some additional information, has been created and placed in the `sf_bikes` folder. The data file is named `flows.geojson` and, in case you are interested, the (Python) code required to created from the original files in the SF Data Portal is also available on the `flows_prep.ipynb` notebook [\[url\]](https://github.com/darribas/spa_notes/blob/master/sf_bikes/flows_prep.ipynb), also in the same folder. @@ -45,8 +41,6 @@ Let us then directly load the file with all the information necessary: ```{r} db <- st_read('./data/sf_bikes/flows.geojson') -#rownames(db@data) <- db$flow_id -#db@data$flow_id <- NULL ``` Note how the interface is slightly different since we are reading a `GeoJSON` file instead of a shapefile. @@ -63,13 +57,13 @@ where `orig` and `dest` are the station IDs of the origin and destination, `stre The easiest way to get a quick preview of what the data looks like spatially is to make a simple plot: -```{r fig.margin=TRUE, fig.cap="Potential routes"} +```{r} plot(db$geometry) ``` Equally, if we want to visualize a single route, we can simply subset the table. For example, to get the shape of the trip from station `39` to station `48`, we can: -```{r fig.margin=TRUE, fig.cap="Trip from station 39 to 48"} +```{r} db %>% dplyr::filter(orig == 39 & dest == 48) %>% ggplot(data = .) + @@ -82,52 +76,52 @@ db %>% or, for the most popular route, we can: -```{r fig.margin=TRUE, fig.cap="Most popular trip"} +```{r} most_pop <- db %>% dplyr::filter(trips15 == max(trips15)) ``` These however do not reveal a lot: there is no geographical context (*why are there so many routes along the NE?*) and no sense of how volumes of bikers are allocated along different routes. Let us fix those two. -The easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using `ggmap`: +The easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using `basemapR`: -```{r, message = FALSE, warning=FALSE} +```{r} +# create a bounding box bbox_db <- st_bbox(db) -names(bbox_db) <- c("left", "bottom", "right", "top") +# download a basemap using ggplot and basemapR +ggplot() + + base_map(bbox_db, increase_zoom = 2, basemap = "positron") + + geom_sf(data = db, fill = NA, colour = "transparent") -SanFran <- get_stamenmap( - bbox_db, - zoom = 14, - maptype = "toner-lite" - ) - -``` - -and make sure it looks like we intend it to look: - -```{r fig.margin=TRUE} -ggmap(SanFran) ``` Now to combine tiles and routes, we need to pull out the coordinates that make up each line. For the route example above, this would be: -```{r fig.margin=TRUE} +```{r} xys1 <- as.data.frame(st_coordinates(most_pop)) ``` -Now we can plot the route[^05-flows-1] (note we also dim down the background to focus the attention on flows): +Now we can plot the route (note we also dim down the background to focus the attention on flows): -[^05-flows-1]: **EXERCISE**: *can you plot the route for the largest climb?* +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -```{r fig.margin=TRUE} -ggmap(SanFran, darken=0.5) + - geom_path( - aes(x=X, y=Y), - data=xys1, - size=1, - color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784), - lineend='round' - ) +Can you plot the route for the largest climb? +::: +::: + +```{r} +#| warning: false +ggplot() + + base_map(bbox_db, increase_zoom = 2, basemap = "dark") + + geom_sf( data = db, fill = NA, colour = "transparent") + + geom_path( data = xys1, + aes( x = X, y = Y ), + #size = 1, + color = "green", + lineend ='round' + ) ``` Now we can plot all of the lines by using a short `for` loop to build up the table: @@ -157,44 +151,57 @@ for(x in 1:nrow(db)){ Now we can go on and plot all of them: -```{r fig.margin=TRUE} -ggmap(SanFran, darken=0.75) + - geom_path( - aes(x=lon, y=lat, group=id), - data=lines, - size=0.1, - color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784), - lineend='round' - ) +```{r} +#| warning: false +ggplot() + + # call basemap + base_map(bbox_db, increase_zoom = 2, basemap = "dark") + + geom_sf(data = db, fill = NA, colour = "transparent") + + # add data + geom_path( data = lines, + aes(x=lon, y=lat, + group=id + ), + size = 1, + color = "green", + lineend = 'round') + ``` Finally, we can get a sense of the distribution of the flows by associating a color gradient to each flow based on its number of trips: -```{r fig.fullwidth=TRUE} -ggmap(SanFran, darken=0.75) + - geom_path( - aes(x=lon, y=lat, group=id, colour=trips), - data=lines, - size=log1p(lines$trips / max(lines$trips)), - lineend='round' - ) + +```{r} +#| message: false +#| warning: false +ggplot() + + # call basemap + base_map(bbox_db, increase_zoom = 2, basemap = "dark") + + geom_sf(data = db, fill = NA, colour = "transparent") + + # add flow data + geom_path( data = lines, + aes(x = lon, y = lat, group = id, colour = trips ), + size=log1p(lines$trips / max(lines$trips)), + lineend = 'round' + ) + + # create a colour palette scale_colour_gradient( - low='grey', high='#07eda0' + low='#440154FF', high='#FDE725FF' ) + theme( axis.text.x = element_blank(), axis.text.y = element_blank(), axis.ticks = element_blank() ) + ``` Note how we transform the size so it's a proportion of the largest trip and then it is compressed with a logarithm. ## Modelling flows -Now we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from @gelman2006data such as predictive performance checks[^05-flows-2] (PPC) +Now we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from @gelman2006data such as predictive performance checks[^05-flows-1] (PPC) -[^05-flows-2]: For a more elaborate introduction to PPC, have a look at Chapters 7 and 8. +[^05-flows-1]: For a more elaborate introduction to PPC, have a look at Chapters 7 and 8. Before we start running regressions, let us first standardize the predictors so we can interpret the intercept as the average flow when all the predictors take the average value, and so we can interpret the model coefficients as changes in standard deviation units: @@ -222,14 +229,14 @@ summary(m1) To explore how good this model is, we will be comparing the predictions the model makes about the number of trips each flow should have with the actual number of trips. A first approach is to simply plot the distribution of both variables: -```{r fig.margin=TRUE} +```{r} plot( - density(m1$fitted.values), - xlim=c(-100, max(db_std$trips15)), + density( m1$fitted.values ), + xlim = c(-100, max( db_std$trips15 )), main='' ) lines( - density(db_std$trips15), + density( db_std$trips15 ), col='red', main='' ) @@ -240,6 +247,7 @@ legend( lwd=1 ) title(main="Predictive check, point estimates - Baseline model") + ``` The plot makes pretty obvious that our initial model captures very few aspects of the distribution we want to explain. However, we should not get too attached to this plot just yet. What it is showing is the distribution of predicted *point* estimates from our model. Since our model is not deterministic but inferential, there is a certain degree of uncertainty attached to its predictions, and that is completely absent from this plot. @@ -257,62 +265,62 @@ Technically speaking, to do this, we need to build a mechanism to obtain a possi ```{r} generate_draw <- function(m){ # Set up predictors matrix - x <- model.matrix(m) + x <- model.matrix( m ) # Obtain draws of parameters (inferential uncertainty) - sim_bs <- sim(m, 1) + sim_bs <- sim( m, 1) # Predicted value mu <- x %*% sim_bs@coef[1, ] # Draw - n <- length(mu) - y_hat <- rnorm(n, mu, sim_bs@sigma[1]) + n <- length( mu ) + y_hat <- rnorm( n, mu, sim_bs@sigma[1]) return(y_hat) } ``` This function takes a model `m` and the set of covariates `x` used and returns a random realization of predictions from the model. To get a sense of how this works, we can get and plot a realization of the model, compared to the expected one and the actual values: -```{r fig.margin=TRUE} +```{r} new_y <- generate_draw(m1) plot( - density(m1$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m1$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m1$fitted.values ), + xlim = c(-100, max( db_std$trips15 )), + ylim = c(0, max(c( + max( density( m1$fitted.values)$y ), + max( density( db_std$trips15)$y ) ) ) ), - col='black', - main='' + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) lines( - density(new_y), - col='green', - main='' + density( new_y ), + col = 'green', + main = '' ) legend( 'topright', c('Predicted', 'Actual', 'Simulated'), - col=c('black', 'red', 'green'), - lwd=1 + col = c( 'black', 'red', 'green' ), + lwd = 1 ) ``` Once we have this "draw engine", we can set it to work as many times as we want using a simple `for` loop. In fact, we can directly plot these lines as compared to the expected one and the trip count: -```{r fig.fullwidth=TRUE} +```{r} plot( - density(m1$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m1$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m1$fitted.values ), + xlim = c(-100, max( db_std$trips15 )), + ylim = c(0, max(c( + max( density( m1$fitted.values)$y ), + max( density( db_std$trips15)$y ) ) ) ), @@ -321,45 +329,45 @@ plot( ) # Loop for realizations for(i in 1:250){ - tmp_y <- generate_draw(m1) - lines(density(tmp_y), - col='grey', - lwd=0.1 + tmp_y <- generate_draw( m1 ) + lines( density( tmp_y ), + col = 'grey', + lwd = 0.1 ) } # lines( - density(m1$fitted.values), - col='black', - main='' + density( m1$fitted.values ), + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) legend( 'topright', c('Actual', 'Predicted', 'Simulated (n=250)'), - col=c('red', 'black', 'grey'), - lwd=1 + col = c('red', 'black', 'grey'), + lwd = 1 ) title(main="Predictive check - Baseline model") ``` -The plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our "inferential engine", which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model[^05-flows-3] is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution. +The plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our "inferential engine", which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model[^05-flows-2] is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution. -[^05-flows-3]: The $R^2$ of our model is around 2% +[^05-flows-2]: The $R^2$ of our model is around 2% -It is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate[^05-flows-4], our capabilities to infer about the more general underlying process are fairly limited. +It is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate[^05-flows-3], our capabilities to infer about the more general underlying process are fairly limited. -[^05-flows-4]: which they are not really, in light of the comparison between the black and red lines. +[^05-flows-3]: which they are not really, in light of the comparison between the black and red lines. **Improving the model** -The bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular[^05-flows-5]. These three routes are aligned around the following principles: +The bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular[^05-flows-4]. These three routes are aligned around the following principles: -[^05-flows-5]: These principles are general and can be applied to pretty much any modeling exercise you run into. The specific approaches we take in this note relate to spatial interaction models +[^05-flows-4]: These principles are general and can be applied to pretty much any modeling exercise you run into. The specific approaches we take in this note relate to spatial interaction models 1. Use better approximations to model your dependent variable. 2. Recognize the structure of your data. @@ -367,9 +375,9 @@ The bad news from the previous section is that our initial model is not great at - **Use better approximations to model your dependent variable** -Standard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for[^05-flows-6]; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal. +Standard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for[^05-flows-5]; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal. -[^05-flows-6]: For an illustration of this, consider the amount of probability mass to the left of zero in the predictive checks above. +[^05-flows-5]: For an illustration of this, consider the amount of probability mass to the left of zero in the predictive checks above. One of the most common distributions for this cases is the Poisson, which can be incorporated through a general linear model (or GLM). The underlying assumption here is that instead of $T_{ij} \sim N(\mu_{ij}, \sigma)$, our model now follows: @@ -389,31 +397,31 @@ m2 <- glm( Now let's see how much better, if any, this approach is. To get a quick overview, we can simply plot the point predictions: -```{r fig.margin=TRUE} +```{r} plot( - density(m2$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m2$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m2$fitted.values ), + xlim = c( -100, max( db_std$trips15 )), + ylim = c( 0, max(c( + max( density( m2$fitted.values)$y ), + max( density(db_std$trips15)$y ) ) ) ), - col='black', - main='' + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) legend( 'topright', c('Predicted', 'Actual'), - col=c('black', 'red'), - lwd=1 + col = c('black', 'red'), + lwd = 1 ) -title(main="Predictive check, point estimates - Poisson model") +title(main = "Predictive check, point estimates - Poisson model") ``` To incorporate uncertainty to these predictions, we need to tweak our `generate_draw` function so it accommodates the fact that our model is not linear anymore. @@ -421,16 +429,16 @@ To incorporate uncertainty to these predictions, we need to tweak our `generate_ ```{r} generate_draw_poi <- function(m){ # Set up predictors matrix - x <- model.matrix(m) + x <- model.matrix( m ) # Obtain draws of parameters (inferential uncertainty) - sim_bs <- sim(m, 1) + sim_bs <- sim( m, 1 ) # Predicted value xb <- x %*% sim_bs@coef[1, ] #xb <- x %*% m$coefficients # Transform using the link function - mu <- exp(xb) + mu <- exp( xb ) # Obtain a random realization - y_hat <- rpois(n=length(mu), lambda=mu) + y_hat <- rpois( n = length( mu ), lambda = mu) return(y_hat) } ``` @@ -439,44 +447,44 @@ And then we can examine both point predictions an uncertainty around them: ```{r fig.fullwidth=TRUE} plot( - density(m2$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m2$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m2$fitted.values ), + xlim = c(-100, max( db_std$trips15 )), + ylim = c(0, max(c( + max( density( m2$fitted.values)$y ), + max( density( db_std$trips15)$y ) ) ) ), - col='white', - main='' + col = 'white', + main = '' ) # Loop for realizations for(i in 1:250){ - tmp_y <- generate_draw_poi(m2) + tmp_y <- generate_draw_poi( m2 ) lines( - density(tmp_y), - col='grey', - lwd=0.1 + density( tmp_y ), + col = 'grey', + lwd = 0.1 ) } # lines( - density(m2$fitted.values), - col='black', - main='' + density( m2$fitted.values ), + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) legend( 'topright', c('Predicted', 'Actual', 'Simulated (n=250)'), - col=c('black', 'red', 'grey'), - lwd=1 + col = c('black', 'red', 'grey'), + lwd = 1 ) -title(main="Predictive check - Poisson model") +title( main = "Predictive check - Poisson model") ``` Voila! Although the curve is still a bit off, centered too much to the right of the actual data, our predictive simulation leaves the fitted values right in the middle. This speaks to a better fit of the model to the actual distribution othe original data follow. @@ -491,47 +499,47 @@ $$ T_{ij} = X_{ij}\beta + \delta_i + \delta_j + \epsilon_{ij} $$ -where $\delta_i$ and $\delta_j$ are origin and destination station fixed effects[^05-flows-7], and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce `orig` and `dest` in the the model: +where $\delta_i$ and $\delta_j$ are origin and destination station fixed effects[^05-flows-6], and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce `orig` and `dest` in the the model: -[^05-flows-7]: In this session, $\delta_i$ and $\delta_j$ are estimated as independent variables so their estimates are similar to interpret to those in $\beta$. An alternative approach could be to model them as random effects in a multilevel framework. +[^05-flows-6]: In this session, $\delta_i$ and $\delta_j$ are estimated as independent variables so their estimates are similar to interpret to those in $\beta$. An alternative approach could be to model them as random effects in a multilevel framework. ```{r} m3 <- glm( 'trips15 ~ straight_dist + total_up + total_down + orig + dest', - data=db_std, - family=poisson + data = db_std, + family = poisson ) ``` -And with our new model, we can have a look at how well it does at predicting the overall number of trips[^05-flows-8]: +And with our new model, we can have a look at how well it does at predicting the overall number of trips[^05-flows-7]: -[^05-flows-8]: Although, theoretically, we could also include simulations of the model in the plot to get a better sense of the uncertainty behind our model, in practice this seems troublesome. The problems most likely arise from the fact that many of the origin and destination binary variable coefficients are estimated with a great deal of uncertainty. This causes some of the simulation to generate extreme values that, when passed through the exponential term of the Poisson link function, cause problems. If anything, this is testimony of how a simple fixed effect model can sometimes lack accuracy and generate very uncertain estimates. A potential extension to work around these problems could be to fit a multilevel model with two specific levels beyond the trip-level: one for origin and another one for destination stations. +[^05-flows-7]: Although, theoretically, we could also include simulations of the model in the plot to get a better sense of the uncertainty behind our model, in practice this seems troublesome. The problems most likely arise from the fact that many of the origin and destination binary variable coefficients are estimated with a great deal of uncertainty. This causes some of the simulation to generate extreme values that, when passed through the exponential term of the Poisson link function, cause problems. If anything, this is testimony of how a simple fixed effect model can sometimes lack accuracy and generate very uncertain estimates. A potential extension to work around these problems could be to fit a multilevel model with two specific levels beyond the trip-level: one for origin and another one for destination stations. -```{r fig.fullwidth=TRUE} +```{r} plot( - density(m3$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m3$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m3$fitted.values ), + xlim = c(-100, max( db_std$trips15 )), + ylim = c(0, max(c( + max( density(m3$fitted.values)$y ), + max( density(db_std$trips15)$y ) ) ) ), - col='black', - main='' + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) legend( 'topright', c('Predicted', 'Actual'), - col=c('black', 'red'), - lwd=1 + col = c('black', 'red'), + lwd = 1 ) -title(main="Predictive check - Orig/dest FE Poisson model") +title( main = "Predictive check - Orig/dest FE Poisson model") ``` That looks significantly better, doesn't it? In fact, our model now better accounts for the long tail where a few routes take a lot of trips. This is likely because the distribution of trips is far from random across stations and our origin and destination fixed effects do a decent job at accounting for that structure. However our model is still notably underpredicting less popular routes and overpredicting routes with above average number of trips. Maybe we should think about moving beyond a simple linear model. @@ -545,38 +553,38 @@ As an exampe of this approach, we can replace the straight distance measurements ```{r} m4 <- glm( 'trips15 ~ street_dist + total_up + total_down + orig + dest', - data=db_std, - family=poisson + data = db_std, + family = poisson ) ``` And we can similarly get a sense of our predictive fitting with: -```{r fig.fullwidth=TRUE} +```{r} plot( - density(m4$fitted.values), - xlim=c(-100, max(db_std$trips15)), - ylim=c(0, max(c( - max(density(m4$fitted.values)$y), - max(density(db_std$trips15)$y) + density( m4$fitted.values ), + xlim = c(-100, max( db_std$trips15)), + ylim = c(0, max(c( + max( density(m4$fitted.values)$y ), + max( density(db_std$trips15)$y ) ) ) ), - col='black', - main='' + col = 'black', + main = '' ) lines( - density(db_std$trips15), - col='red', - main='' + density( db_std$trips15 ), + col = 'red', + main = '' ) legend( 'topright', c('Predicted', 'Actual'), - col=c('black', 'red'), - lwd=1 + col = c('black', 'red'), + lwd = 1 ) -title(main="Predictive check - Orig/dest FE Poisson model") +title( main = "Predictive check - Orig/dest FE Poisson model") ``` Hard to tell any noticeable difference, right? To see if there is any, we can have a look at the estimates obtained: @@ -591,17 +599,17 @@ And compare this to that of the straight distances in the previous model: summary(m3)$coefficients['straight_dist', ] ``` -As we can see, the differences exist but they are not massive. Let's use this example to learn how to interpret coefficients in a Poisson model[^05-flows-9]. Effectively, these estimates can be understood as multiplicative effects. Since our model fits +As we can see, the differences exist but they are not massive. Let's use this example to learn how to interpret coefficients in a Poisson model[^05-flows-8]. Effectively, these estimates can be understood as multiplicative effects. Since our model fits -[^05-flows-9]: See section 6.2 of @gelman2006data for a similar treatment of these. +[^05-flows-8]: See section 6.2 of @gelman2006data for a similar treatment of these. $$ T_{ij} \sim Poisson (\exp^{X_{ij}\beta}) $$ -we need to transform $\beta$ through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is $\beta_{street} = -0.0996$, but this needs to be translated through the exponential into $e^{-0.0996} = 0.906$. In other words, since distance is expressed in standard deviations[^05-flows-10], we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with $e^{-0.0782} = 0.925$ for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m). +we need to transform $\beta$ through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is $\beta_{street} = -0.0996$, but this needs to be translated through the exponential into $e^{-0.0996} = 0.906$. In other words, since distance is expressed in standard deviations[^05-flows-9], we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with $e^{-0.0782} = 0.925$ for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m). -[^05-flows-10]: Remember the transformation at the very beginning. +[^05-flows-9]: Remember the transformation at the very beginning. ## Predicting flows @@ -635,9 +643,15 @@ rmse_m4 <- rmse(db_std$trips16, m4$fitted.values) rmse_m4 ``` -That means that, on average, predictions in our best model `m4` are 256 trips off. Is this good? Bad? Worse? It's hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year's counts:[^05-flows-11] +That means that, on average, predictions in our best model `m4` are 256 trips off. Is this good? Bad? Worse? It's hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year's counts:\[\^05-flows-11\] + +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -[^05-flows-11]: **EXERCISE**: can you create a single plot that displays the distribution of the predicted values of the five different ways to predict trips in 2016 and the actual counts of trips? +Can you create a single plot that displays the distribution of the predicted values of the five different ways to predict trips in 2016 and the actual counts of trips? +::: +::: ```{r} rmses <- data.frame( diff --git a/06-spatial-econometrics.qmd b/06-spatial-econometrics.qmd index 05564b7..8945b92 100644 --- a/06-spatial-econometrics.qmd +++ b/06-spatial-econometrics.qmd @@ -5,39 +5,25 @@ This chapter is based on the following references, which are good follow-up's on - [Chapter 11](https://geographicdata.science/book/notebooks/11_regression.html) of the GDS Book, by @reyABwolf. - [Session III](http://darribas.org/sdar_mini/notes/Class_03.html) of @arribas2014spatial. Check the "Related readings" section on the session page for more in-depth discussions. - @anselin2005spatial, freely available to download \[[`pdf`](https://dces.wisc.edu/wp-content/uploads/sites/128/2013/08/W14_Anselin2007.pdf)\]. -- The second part of this tutorial assumes you have reviewed [Block E](https://darribas.org/gds_course/content/bE/concepts_E.html) of @darribas_gds_course. +- The second part of this tutorial assumes you have reviewed [the Spatial Weights Section](https://darribas.org/gds_course/content/bE/concepts_E.html) of @darribas_gds_course. ## Dependencies We will rely on the following libraries in this section, all of them included in @sec-dependencies: -```{r results='hide', message = FALSE} -# Layout -library(tufte) -# For pretty table -library(knitr) -# For string parsing -library(stringr) +```{r} +#| warning: false +#| message: false +# Data management +library(tidyverse) # Spatial Data management -library(rgdal) -# Pretty graphics -library(ggplot2) -# Pretty maps -library(ggmap) +library(sf) # For all your interpolation needs library(gstat) -# For data manipulation -library(dplyr) # Spatial regression library(spdep) ``` -Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with `setwd()`. Please replace in the following line the path to the folder where you have placed this file and where the `house_transactions` folder with the data lives. - -```{r} -setwd('.') -``` - ## Data To explore ideas in spatial regression, we will the set of Airbnb properties for San Diego (US), borrowed from the "Geographic Data Science with Python" book (see [here](https://geographicdata.science/book/data/airbnb/regression_cleaning.html) for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region. @@ -80,9 +66,15 @@ $$ \log(P) = \alpha + \beta_1 Acc + \beta_2 Bath + \beta_3 Bedr + \beta_4 Beds + \epsilon $$ where each term can be interpreted in terms of vectors instead of scalars (wit the exception of the parameters $(\alpha, \beta_{1, 2, 3, 4})$, which *are* scalars). Note we are using the logarithm of the price, since this allows us to interpret the coefficients as roughly the percentage change induced by a unit increase in the explanatory variable of the estimate. -Remember a regression can be seen as a multivariate extension of bivariate correlations. Indeed, one way to interpret the $\beta_k$ coefficients in the equation above is as the degree of correlation between the explanatory variable $k$ and the dependent variable, *keeping all the other explanatory variables constant*. When you calculate simple bivariate correlations, the coefficient of a variable is picking up the correlation between the variables, but it is also subsuming into it variation associated with other correlated variables --also called confounding factors[^06-spatial-econometrics-1]. Regression allows you to isolate the distinct effect that a single variable has on the dependent one, once we *control* for those other variables. +Remember a regression can be seen as a multivariate extension of bivariate correlations. Indeed, one way to interpret the $\beta_k$ coefficients in the equation above is as the degree of correlation between the explanatory variable $k$ and the dependent variable, *keeping all the other explanatory variables constant*. When you calculate simple bivariate correlations, the coefficient of a variable is picking up the correlation between the variables, but it is also subsuming into it variation associated with other correlated variables --also called confounding factors\[\^06-spatial-econometrics-1\]. Regression allows you to isolate the distinct effect that a single variable has on the dependent one, once we *control* for those other variables. + +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** -[^06-spatial-econometrics-1]: **EXAMPLE** Assume that new houses tend to be built more often in areas with low deprivation. If that is the case, then $NEW$ and $IMD$ will be correlated with each other (as well as with the price of a house, as we are hypothesizing in this case). If we calculate a simple correlation between $P$ and $IMD$, the coefficient will represent the degree of association between both variables, but it will also include some of the association between $IMD$ and $NEW$. That is, part of the obtained correlation coefficient will be due not to the fact that higher prices tend to be found in areas with low IMD, but to the fact that new houses tend to be more expensive. This is because (in this example) new houses tend to be built in areas with low deprivation and simple bivariate correlation cannot account for that. +Assume that new houses tend to be built more often in areas with low deprivation. If that is the case, then $NEW$ and $IMD$ will be correlated with each other (as well as with the price of a house, as we are hypothesizing in this case). If we calculate a simple correlation between $P$ and $IMD$, the coefficient will represent the degree of association between both variables, but it will also include some of the association between $IMD$ and $NEW$. That is, part of the obtained correlation coefficient will be due not to the fact that higher prices tend to be found in areas with low IMD, but to the fact that new houses tend to be more expensive. This is because (in this example) new houses tend to be built in areas with low deprivation and simple bivariate correlation cannot account for that. +::: +::: Practically speaking, running linear regressions in `R` is straightforward. For example, to fit the model specified in the equation above, we only need one line of code: @@ -98,9 +90,9 @@ In order to inspect the results of the model, the quickest way is to call `summa summary(m1) ``` -A full detailed explanation of the output is beyond the scope of the chapter, but we will highlight the relevant bits for our main purpose. This is concentrated on the `Coefficients` section, which gives us the estimates for the $\beta_k$ coefficients in our model. These estimates are the raw equivalent of the correlation coefficient between each explanatory variable and the dependent one, once the "polluting" effect of the other variables included in the model has been accounted for[^06-spatial-econometrics-2]. Results are as expected for the most part: houses tend to be significantly more expensive if they accommodate more people (an extra person increases the price by `r round(m1$coefficients[["accommodates"]], 3) * 100`%, approximately), have more bathrooms (`r round(m1$coefficients[["bathrooms"]], 3) * 100`%), or bedrooms (`r round(m1$coefficients[["bedrooms"]], 3) * 100`%). Perhaps counter intuitively, an extra bed available seems to decrease the price by about `r round(m1$coefficients[["beds"]], 3) * 100`%. However, keep in mind that this is the case, *everything else equal*. Hence, more beds per room and bathroom (ie. a more crowded house) is a bit cheaper. +A full detailed explanation of the output is beyond the scope of the chapter, but we will highlight the relevant bits for our main purpose. This is concentrated on the `Coefficients` section, which gives us the estimates for the $\beta_k$ coefficients in our model. These estimates are the raw equivalent of the correlation coefficient between each explanatory variable and the dependent one, once the "polluting" effect of the other variables included in the model has been accounted for[^06-spatial-econometrics-1]. Results are as expected for the most part: houses tend to be significantly more expensive if they accommodate more people (an extra person increases the price by `r round(m1$coefficients[["accommodates"]], 3) * 100`%, approximately), have more bathrooms (`r round(m1$coefficients[["bathrooms"]], 3) * 100`%), or bedrooms (`r round(m1$coefficients[["bedrooms"]], 3) * 100`%). Perhaps counter intuitively, an extra bed available seems to decrease the price by about `r round(m1$coefficients[["beds"]], 3) * 100`%. However, keep in mind that this is the case, *everything else equal*. Hence, more beds per room and bathroom (ie. a more crowded house) is a bit cheaper. -[^06-spatial-econometrics-2]: Keep in mind that regression is no magic. We are only discounting the effect of other confounding factors that we include in the model, not of *all* potentially confounding factors. +[^06-spatial-econometrics-1]: Keep in mind that regression is no magic. We are only discounting the effect of other confounding factors that we include in the model, not of *all* potentially confounding factors. ## Spatial regression: a (very) first dip @@ -174,9 +166,7 @@ Remember that the interpretation of a $\beta_k$ coefficient is the effect of var **Spatial regimes** -At the core of estimating spatial FEs is the idea that, instead of assuming the dependent variable behaves uniformly over space, there are systematic effects following a geographical pattern that affect its behaviour. In other words, spatial FEs introduce econometrically the notion of spatial heterogeneity. They do this in the simplest possible form: by allowing the constant term to vary geographically. The other elements of the regression are left untouched and hence apply uniformly across space. The idea of spatial regimes (SRs) is to generalize the spatial FE approach to allow not only the constant term to vary but also any other explanatory variable. This implies that the equation we will be estimating is: $$ -\log(P_i) = \alpha_r + \beta_{1r} Acc_i + \beta_{2r} Bath_i + \beta_{3r} Bedr_i + \beta_{4r} Beds_i + \epsilon_i -$$ +At the core of estimating spatial FEs is the idea that, instead of assuming the dependent variable behaves uniformly over space, there are systematic effects following a geographical pattern that affect its behaviour. In other words, spatial FEs introduce econometrically the notion of spatial heterogeneity. They do this in the simplest possible form: by allowing the constant term to vary geographically. The other elements of the regression are left untouched and hence apply uniformly across space. The idea of spatial regimes (SRs) is to generalize the spatial FE approach to allow not only the constant term to vary but also any other explanatory variable. This implies that the equation we will be estimating is: $$\log(P_i) = \alpha_r + \beta_{1r} Acc_i + \beta_{2r} Bath_i + \beta_{3r} Bedr_i + \beta_{4r} Beds_i + \epsilon_i$$ where we are not only allowing the constant term to vary by region ($\alpha_r$), but also every other parameter ($\beta_{kr}$). @@ -191,7 +181,7 @@ Then, the estimation leverages the capabilities in model description of R formul ```{r} # `:` notation implies interaction variables m3 <- lm( - 'log_price ~ 0 + (accommodates + bathrooms + bedrooms + beds):(neighborhood)', + 'log_price ~ (one + accommodates + bathrooms + bedrooms + beds):(neighborhood)', db ) summary(m3) @@ -205,9 +195,9 @@ As we have just discussed, SH is about effects of phenomena that are *explicitly **Spatial Weights** -There are several ways to introduce spatial dependence in an econometric framework, with varying degrees of econometric sophistication [see @anselin2003spatial for a good overview]. Common to all of them however is the way space is formally encapsulated: through *spatial weights matrices (*$W$)[^06-spatial-econometrics-3] These are $NxN$ matrices with zero diagonals and every $w_{ij}$ cell with a value that represents the degree of spatial connectivity/interaction between observations $i$ and $j$. If they are not connected at all, $w_{ij}=0$, otherwise $w_{ij}>0$ and we call $i$ and $j$ neighbors. The exact value in the latter case depends on the criterium we use to define neighborhood relations. These matrices also tend to be row-standardized so the sum of each row equals to one. +There are several ways to introduce spatial dependence in an econometric framework, with varying degrees of econometric sophistication [see @anselin2003spatial for a good overview]. Common to all of them however is the way space is formally encapsulated: through *spatial weights matrices (*$W$)[^06-spatial-econometrics-2] These are $NxN$ matrices with zero diagonals and every $w_{ij}$ cell with a value that represents the degree of spatial connectivity/interaction between observations $i$ and $j$. If they are not connected at all, $w_{ij}=0$, otherwise $w_{ij}>0$ and we call $i$ and $j$ neighbors. The exact value in the latter case depends on the criterium we use to define neighborhood relations. These matrices also tend to be row-standardized so the sum of each row equals to one. -[^06-spatial-econometrics-3]: If you need to refresh your knowledge on spatial weight matrices, check [Block E](https://darribas.org/gds_course/content/bE/concepts_E.html) of @darribas_gds_course; [Chapter 4](https://geographicdata.science/book/notebooks/04_spatial_weights.html) of @reyABwolf; or the [Spatial Weights](https://fcorowe.github.io/intro-gds/03-spatial_weights.html) Section of @rowe2022a. +[^06-spatial-econometrics-2]: If you need to refresh your knowledge on spatial weight matrices, check [Block E](https://darribas.org/gds_course/content/bE/concepts_E.html) of @darribas_gds_course; [Chapter 4](https://geographicdata.science/book/notebooks/04_spatial_weights.html) of @reyABwolf; or the [Spatial Weights](https://fcorowe.github.io/intro-gds/03-spatial_weights.html) Section of @rowe2022a. A related concept to spatial weight matrices is that of *spatial lag*. This is an operator that multiplies a given variable $y$ by a spatial weight matrix: @@ -307,8 +297,9 @@ The core idea is that once you have estimates for the way in which the explanato Conceptually, predicting in linear regression models involves using the estimates of the parameters to obtain a value for the dependent variable: $$ -\bar{\log(P_i)} = \bar{\alpha} + \bar{\beta_1} Acc_i^* + \bar{\beta_2 Bath_i^*} + \bar{\beta_3} Bedr_i^* + \bar{\beta_4} Beds_i^* -$$ where $\bar{\log{P_i}}$ is our predicted value, and we include the $\bar{}$ sign to note that it is our estimate obtained from fitting the model. We use the $^*$ sign to note that those can be new values for the explanatory variables, not necessarily those used to fit the model. +\log(\bar{P_i}) = \bar{\alpha} + \bar{\beta_1} Acc_i^* + \bar{\beta_2} Bath_i^* + \bar{\beta_3} Bedr_i^* + \bar{\beta_4} Beds_i^* +$$ +where $\log(\bar{P_i})$ is our predicted value, and we include the bar sign to note that it is our estimate obtained from fitting the model. We use the $^*$ sign to note that those can be new values for the explanatory variables, not necessarily those used to fit the model. Technically speaking, prediction in linear models is relatively streamlined in R. Suppose we are given data for a new house which is to be put on the AirBnb platform. We know it accommodates four people, and has two bedrooms, three beds, and one bathroom. We also know that the surrounding properties have, on average, 1.5 bathrooms. Let us record the data first: diff --git a/07-multilevel-01.qmd b/07-multilevel-01.qmd index b9ecb44..872851c 100644 --- a/07-multilevel-01.qmd +++ b/07-multilevel-01.qmd @@ -7,31 +7,32 @@ This chapter provides an introduction to multi-level data structures and multi-l ## Dependencies -This chapter uses the following libraries which are listed in the \[Dependency list\] Section of Chapter 1: +We will use the following dependencies -```{r, message = FALSE} +```{r} +#| include = FALSE +source("./style/data-visualisation_theme.R") +``` + +```{r} +#| message: false +#| warning: false # Data manipulation, transformation and visualisation library(tidyverse) # Nice tables library(kableExtra) -# Simple features (a standardised way to encode vector data ie. points, lines, polygons) +# Spatial data manitulation library(sf) -# Spatial objects conversion -library(sp) # Thematic maps library(tmap) # Colour palettes -library(RColorBrewer) -# More colour palettes -library(viridis) # nice colour schemes +library(viridis) # Fitting multilevel models library(lme4) # Tools for extracting information generated by lme4 library(merTools) # Exportable regression tables library(jtools) -library(stargazer) -library(sjPlot) ``` ## Data @@ -42,9 +43,7 @@ For this chapter, we will data for Liverpool from England's 2011 Census. The ori Let us read the data: -```{r, results = 'hide'} -# clean workspace -rm(list=ls()) +```{r} # read data oa_shp <- st_read("data/mlm/OA.shp") ``` @@ -52,6 +51,7 @@ oa_shp <- st_read("data/mlm/OA.shp") We can now attach and visualise the structure of the data. ```{r} +#| message: false # attach data frame attach(oa_shp) @@ -112,8 +112,8 @@ We first need to want to understand our dependent variable: its density ditribut ```{r} ggplot(data = oa_shp) + -geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = unemp)) + - theme_classic() + geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = unemp)) + + theme_plot_tufte() ``` ```{r} @@ -186,11 +186,17 @@ model2 <- lmer(eq2, data = oa_shp) summary(model2) ``` -We can estimate a three-level model by adding `(1 | msoa_cd)` to allow the intercept to also vary by MSOAs and account for the nesting structure of LSOAs within MSOAs. +We can estimate a three-level model by replacing `(1 | lsoa_cd)` for `(1 | msoa_cd/lsoa_cd)` to allow the intercept to also vary by MSOAs and account for the nesting structure of LSOAs within MSOAs. In multilevel modelling, these types of models are formally known as *nested random effects* and they differ from a different set of models known as *crossed random effects*. + +::: column-margin ::: callout-note A crossed random effect model in our example would be expressed as follows: + +`unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)` + +::: ::: column-margin ```{r} # specify a model equation -eq3 <- unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd) +eq3 <- unemp ~ 1 + (1 | msoa_cd/lsoa_cd) model3 <- lmer(eq3, data = oa_shp) # estimates @@ -230,21 +236,27 @@ We start by examining the fixed effects or estimated model averaging over LSOAs fixef(model3) ``` -Th estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as `0.115288` and is statistically significant at `5%` significance. +Th estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as `0.1152881`. > Random effects -The set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The `lsoa_cd`, `msoa_cd` and `Residual` estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is `0.0007603`, `0.0020735` and `0.0025723`, respectively. +The set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The `lsoa_cd:msoa_cd`, `msoa_cd` and `Residual` estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is `0.0007603`, `0.0020735` and `0.0025723`, respectively. ### Variance Partition Coefficient (VPC) -The purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.[^07-multilevel-01-2] For our case, the VPC at the LSOA level indicates that 14% of the variation in percentage of unemployed resident population across OAs can be explained by differences across LSOAs. What is the VPC at the MSOA level? +The purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.[^07-multilevel-01-2] For our case, the VPC at the MSOA level indicates that 38% of the variation in percentage of unemployed resident population across OAs can be explained by differences across MSOAs. [^07-multilevel-01-2]: The VPC is equal to the intra-class correlation coefficient which is the correlation between the observations of the dependent variable selected randomly from the same group. For instance, if the VPC is 0.1, we would say that 10% of the variation is between groups and 90% within. The correlation between randomly chosen pairs of observations belonging to the same group is 0.1. +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** What is the VPC at the LSOA level? +::: +::: + ```{r} -vpc_lsoa <- 0.0007603 / (0.0007603 + 0.0020735 + 0.0025723) -vpc_lsoa * 100 +vpc_msoa <- 0.0020735 / (0.0007603 + 0.0020735 + 0.0025723) +vpc_msoa * 100 ``` You can also obtain the VPC by executing: @@ -274,7 +286,13 @@ coef_m3 <- coef(model3) head(coef_m3$lsoa_cd,5) ``` -The results indicate that the estimated regression line is $y = 0.09915456$ for LSOA `E01006512`; $y = 0.09889615$ for LSOA `E01006513` and so forth. Try getting the estimated model within each MSOA. +The results indicate that the estimated regression line is $y = 0.09915456$ for LSOA `E01006512` within MSOA `E02001377`; $y = 0.09889615$ for LSOA `E01006513` within MSOA `E02006932` and so forth. + +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** Try getting the estimated model within each MSOA. +::: +::: *Random effects* @@ -291,7 +309,8 @@ We can also obtain group-level errors (*random effects*) by using a simulation a ```{r} # obtain estimates -merTools::REsim(model3) %>% head(10) +merTools::REsim(model3) %>% + head(10) ``` The results contain the estimated mean, median and standard deviation for the intercept within each group (e.g. LSOA). The mean estimates are similar to those obtained from `ranef` with some small differences due to rounding. @@ -303,7 +322,13 @@ To gain an undertanding of the general pattern of the *random effects*, we can u plotREsim(REsim(model3)) ``` -Focusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton. What districts do we have at the other extreme? +Focusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. These are the dots below the horizontal red line. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton. + +::: column-margin +::: {.callout-tip appearance="simple" icon="false"} +**Task** What districts do we have at the other extreme? Have a go at identifying them. +::: +::: ```{r} re <- REsim(model3) @@ -382,10 +407,10 @@ ranef_m4 <- ranef(model4) head(ranef_m4$msoa_cd, 5) ``` -yields an estimated intercept for MSOA `E02001347` which is `0.017474815` lower than the average with a regression line: `(0.04681959 - 0.017474815) + 0.29588110x` `=` `0.02934478 + 0.29588110x`. You can confirm this by looking at the estimated model within each MSOA by executing (remove the `#` sign): +yields an estimated intercept for MSOA `E02001347` which is `0.017474815` lower than the average with a regression line: `(0.04681959 - 0.017474815) + 0.29588110x` `=` `0.02934478 + 0.29588110x`. You can confirm this by looking at the estimated model within each MSOA by executing on the first row: ```{r} -#coef(model4) +coef(model4) %>% head(n = 5 ) ``` *Fixed effect correlations* @@ -407,6 +432,8 @@ $$\beta_{0j} = \beta_{0} + \gamma_{1}m_{j} + u_{0j}$$ where $x_{ij}$ is the OA-level proportion of population suffering long-term illness and $m_{j}$ is the MSOA-level proportion of male population. We first need to create this group-level predictor: ```{r} +#| message: false +#| warning: false # detach OA shp and attach MSOA shp detach(oa_shp) attach(msoa_shp) @@ -430,6 +457,7 @@ head(oa_shp[1:10, c("msoa_cd", "oa_cd", "unemp", "pr_male")]) We can now estimate our model: ```{r} +#| message: false detach(msoa_shp) attach(oa_shp) diff --git a/08-multilevel-02.qmd b/08-multilevel-02.qmd index a60bf0c..36d0e96 100644 --- a/08-multilevel-02.qmd +++ b/08-multilevel-02.qmd @@ -24,17 +24,13 @@ library(sp) # Thematic maps library(tmap) # Colour palettes -library(RColorBrewer) -# More colour palettes -library(viridis) # nice colour schemes +library(viridis) # Fitting multilevel models library(lme4) # Tools for extracting information generated by lme4 library(merTools) # Exportable regression tables library(jtools) -library(stargazer) -library(sjPlot) ``` ## Data diff --git a/09-gwr.qmd b/09-gwr.qmd index 907a3aa..f0a4d06 100644 --- a/09-gwr.qmd +++ b/09-gwr.qmd @@ -33,8 +33,6 @@ library(spgwr) library(corrplot) # Exportable regression tables library(jtools) -library(stargazer) -library(sjPlot) # Assess multicollinearity library(car) ``` diff --git a/_quarto.yml b/_quarto.yml index 93f0df9..00ba1ea 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -33,16 +33,28 @@ bibliography: references.bib format: html: - toc: true - theme: [default, theme.scss] - css: style.css - code-copy: true + theme: + light: [default, style/custom.scss] + fontsize: "16px" + linestretch: 1.6 + mainfont: "Roboto" + monofont: "Fira Mono" + smooth-scroll: true + toc-depth: 3 grid: - sidebar-width: 200px + sidebar-width: 300px body-width: 900px margin-width: 300px - pdf: - documentclass: scrreprt + code-link: true + code-tools: + toggle: true + code-fold: false + highlight-style: printing + code-block-bg: true + code-overflow: wrap + reference-location: margin + mermaid: + theme: neutral editor: visual diff --git a/docs/01-overview.html b/docs/01-overview.html index 8ee24ac..689e258 100644 --- a/docs/01-overview.html +++ b/docs/01-overview.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 1  Overview - @@ -103,14 +70,19 @@ - - - - - +} -
-
- -
- -
+
-
-
- -
-
-

1  Overview

+
+

1  Overview

@@ -282,9 +265,8 @@

https://gdsl-ul.github.io/san/

@@ -292,8 +274,8 @@

ENS453 Spatial Modelling for Data Scientists

-
-

1.1 Aims

+

+1.1 Aims

This module aims to provides students with a range of techniques for analysing and modelling spatial data:

  • build upon the more general research training delivered via companion modules on Data Collection and Data Analysis, both of which have an aspatial focus;
  • @@ -301,10 +283,8 @@

    -

    1.2 Learning Outcomes

    +

+1.2 Learning Outcomes

By the end of the module, students should be able to:

  • identify some key sources of spatial data and resources of spatial analysis and modelling tools;
  • @@ -312,44 +292,46 @@

    apply a range of computer-based techniques for the analysis of spatial data, including mapping, correlation, kernel density estimation, regression, multi-level models, geographically-weighted regression, spatial interaction models and spatial econometrics;
  • apply appropriate analytical strategies to tackle the key methodological challenges facing spatial analysis – spatial autocorrelation, heterogeneity, and ecological fallacy; and,
  • select appropriate analytical tools for analysing specific spatial data sets to address emerging social issues facing the society.
  • -

-
-
-

1.3 Feedback

+

+1.3 Feedback

  • Formal assessment of two computational essays. Written assignment-specific feedback will be provided within three working weeks of the submission deadline. Comments will offer an understanding of the mark awarded and identify areas which can be considered for improvement in future assignments.

  • Verbal face-to-face feedback. Immediate face-to-face feedback will be provided during lecture, discussion and clinic sessions in interaction with staff. This will take place in all live sessions during the semester.

  • Online forum. Asynchronous written feedback will be provided via an online forum maintained by the module lead. Students are encouraged to contribute by asking and answering questions relating to the module content. Staff will monitor the forum Monday to Friday 9am-5pm, but it will be open to students to make contributions at all times.

  • -
-
-
-

1.4 Computational Environment

+

+1.4 Computational Environment

To reproduce the code in the book, you need the following software packages:

    -
  • R-4.2.2
  • -
  • RStudio 2022.12.0-353
  • -
  • Quarto 1.2.280
  • +
  • R-4.3.1
  • +
  • RStudio 2023.09.0+463
  • +
  • Quarto 1.3.450
  • the list of libraries in the next section

To check your version of:

    -
  • R and libraries run sessionInfo()
  • -
  • RStudio click help on the menu bar and then About
  • +
  • R and libraries run sessionInfo() +
  • +
  • RStudio click help on the menu bar and then About +
  • Quarto check the version file in the quarto folder on your computer.

To install and update:

-
-

1.4.1 Dependency list

+

+1.4.1 Dependency list

The list of libraries used in this book is provided below:

  • arm
  • car
  • corrplot
  • +
  • devtools
  • FRK
  • gghighlight
  • ggplot2
  • @@ -380,57 +362,64 @@

    tmap
  • tufte
  • viridis
  • +
  • basemapR

Copy, paste and run the code below in your console. Ensure all packages are installed on your computer.

-
deps <- list(
-    "arm",
-    "car",
-    "corrplot",
-    "FRK",
-    "gghighlight",
-    "ggplot2",
-    "ggmap",
-    "gridExtra",
-    "gstat",
-    "hexbin",
-    "jtools",
-    "kableExtra",
-    "knitr",
-    "lme4",
-    "lmtest",
-    "lubridate",
-    "MASS",
-    "merTools",
-    "plyr",
-    "RColorBrewer",
-    "rgdal",
-    "sf",
-    "sjPlot",
-    "sp",
-    "spgwr",
-    "spatialreg",
-    "spacetime",
-    "stargazer",
-    "tidyverse",
-    "tmap",
-    "tufte",
-    "viridis"
-)
+
# package names
+packages <- c(
+    "arm",
+    "car",
+    "corrplot",
+    "devtools",
+    "FRK",
+    "gghighlight",
+    "ggplot2",
+    "ggmap",
+    "gridExtra",
+    "gstat",
+    "hexbin",
+    "jtools",
+    "kableExtra",
+    "knitr",
+    "lme4",
+    "lmtest",
+    "lubridate",
+    "MASS",
+    "merTools",
+    "plyr",
+    "RColorBrewer",
+    "sf",
+    "sjPlot",
+    "sp",
+    "spgwr",
+    "spatialreg",
+    "spacetime",
+    "stargazer",
+    "tidyverse",
+    "tmap",
+    "tufte",
+    "viridis"
+)
+
+# install packages not yet installed
+installed_packages <- packages %in% rownames(installed.packages())
+if (any(installed_packages == FALSE)) {
+  install.packages(packages[!installed_packages])
+}
+
+# packages loading
+invisible(lapply(packages, library, character.only = TRUE))
-
-
# we can load them all to make sure they are installed:
-for(lib in deps){library(lib, character.only = TRUE)}
-
-
-
-
-

1.5 Assessment

+

::: column-margin ::: callout-note To install the library basemapR, you need to install from source by running:

+

library(devtools)
install_github('Chrisjb/basemapR') ::: ::: column-margin

+

+1.5 Assessment

The final module mark is composed of the two computational essays. Together they are designed to cover the materials introduced in the entirety of content covered during the semester. A computational essay is an essay whose narrative is supported by code and computational results that are included in the essay itself. Each teaching week, you will be required to address a set of questions relating to the module content covered in that week, and to use the material that you will produce for this purpose to build your computational essay.

-

Assignment 1 (50%) refer to the set of questions at the end of Chapter 4, Chapter 5 and Chapter 6. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.

-

Assignment 2 (50%) refer to the set of questions at the end of Chapter 7, Chapter 8, Chapter 9 and Chapter 10. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.

-
-

1.5.1 Format Requirements

+

Assignment 1 (50%) refer to the set of questions at the end of Chapter 4, Chapter 5 and Chapter 6. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.

+

Assignment 2 (50%) refer to the set of questions at the end of Chapter 7, Chapter 8, Chapter 9 and Chapter 10. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.

+

+1.5.1 Format Requirements

Both assignments will have the same requirements:

  • Maximum word count: 2,000 words, excluding figures and references.
  • @@ -440,26 +429,30 @@

    Assignments need to be prepared in “Quarto Document” format (i.e. qmd extension) and then converted into a self-contained HTML file that will then be submitted via Turnitin. The document should only display content that will be assessed. Intermediate steps do not need to be displayed. Messages resulting from loading packages, attaching data frames, or similar messages do not need to be included as output code. Useful resources to customise your R notebook can be found on Quarto’s website.

    Two Quarto Document templates will be available via the module Canvas site.

    Submission is electronic only via Turnitin on Canvas.

    -

-
-

1.5.2 Marking criteria

+

+1.5.2 Marking criteria

The Standard Environmental Sciences School marking criteria apply, with a stronger emphasis on evidencing the use of regression models, critical analysis of results and presentation standards. In addition to these general criteria, the code and outputs (i.e. tables, maps and plots) contained within the notebook submitted for assessment will be assessed according to the extent of documentation and evidence of expertise in changing and extending the code options illustrated in each chapter. Specifically, the following criteria will be applied:

    -
  • 0-15: no documentation and use of default options.
  • -
  • 16-39: little documentation and use of default options.
  • -
  • 40-49: some documentation, and use of default options.
  • -
  • 50-59: extensive documentation, and edit of some of the options provided in the notebook (e.g. change north arrow location).
  • -
  • 60-69: extensive well organised and easy to read documentation, and evidence of understanding of options provided in the code (e.g. tweaking existing options).
  • -
  • 70-79: all above, plus clear evidence of code design skills (e.g. customising graphics, combining plots (or tables) into a single output, adding clear axis labels and variable names on graphic outputs, etc.).
  • -
  • 80-100: all as above, plus code containing novel contributions that extend/improve the functionality the code was provided with (e.g. comparative model assessments, novel methods to perform the task, etc.).
  • +
  • +0-15: no documentation and use of default options.
  • +
  • +16-39: little documentation and use of default options.
  • +
  • +40-49: some documentation, and use of default options.
  • +
  • +50-59: extensive documentation, and edit of some of the options provided in the notebook (e.g. change north arrow location).
  • +
  • +60-69: extensive well organised and easy to read documentation, and evidence of understanding of options provided in the code (e.g. tweaking existing options).
  • +
  • +70-79: all above, plus clear evidence of code design skills (e.g. customising graphics, combining plots (or tables) into a single output, adding clear axis labels and variable names on graphic outputs, etc.).
  • +
  • +80-100: all as above, plus code containing novel contributions that extend/improve the functionality the code was provided with (e.g. comparative model assessments, novel methods to perform the task, etc.).
-
-
+ -

- -
+ \ No newline at end of file diff --git a/docs/02-spatial_data.html b/docs/02-spatial_data.html index 8bb1042..9ffb1b5 100644 --- a/docs/02-spatial_data.html +++ b/docs/02-spatial_data.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 2  Spatial Data - +} @@ -59,14 +90,19 @@ - - - - - +} -
-
- -
- -
+
-
-
- -
-
-

2  Spatial Data

+
+

2  Spatial Data

@@ -236,31 +281,24 @@

-

2.1 Spatial Data types

+

This Chapter seeks to present and describe distinctive attributes of spatial data, and discuss some of the main challenges in analysing and modelling these data. Spatial data is a term used to describe any data associating a given variable attribute to a specific location on the Earth’s surface.

+

+2.1 Spatial Data types

Different classifications of spatial data types exist. Knowing the structure of the data at hand is important as specific analytical methods would be more appropriate for particular data types. We will use a particular classification involving four data types: lattice/areal data, point data, flow data and trajectory data (Fig. 1). This is not a exhaustive list but it is helpful to motivate the analytical and modelling methods that we cover in this book.

-
-

-

Fig. 1. Data Types. Area / Lattice data source: Önnerfors et al. (2019). Point data source: Tao et al. (2018). Flow data source: Rowe and Patias (2020). Trajectory data source: Kwan and Lee (2004).

-
+

+
Fig. 1. Data Types. Area / Lattice data source: Önnerfors et al. (2019). Point data source: Tao et al. (2018). Flow data source: Rowe and Patias (2020). Trajectory data source: Kwan and Lee (2004).

Lattice/Areal Data. These data correspond to records of attribute values (such as population counts) for a fixed geographical area. They may comprise regular shapes (such as grids or pixels) or irregular shapes (such as states, counties or travel-to-work areas). Raster data are a common source of regular lattice/areal area, while censuses are probably the most common form of irregular lattice/areal area. Point data within an area can be aggregated to produce lattice/areal data.

Point Data. These data refer to records of the geographic location of an discrete event, or the number of occurrences of geographical process at a given location. As displayed in Fig. 1, examples include the geographic location of bus stops in a city, or the number of boarding passengers at each bus stop.

Flow Data. These data refer to records of measurements for a pair of geographic point locations. or pair of areas. These data capture the linkage or spatial interaction between two locations. Migration flows between a place of origin and a place of destination is an example of this type of data.

Trajectory Data. These data record geographic locations of moving objects at various points in time. A trajectory is composed of a single string of data recording the geographic location of an object at various points in time and each record in the string contains a time stamp. These data are complex and can be classified into explicit trajectory data and implicit trajectory data. The former refer to well-structured data and record positions of objects continuously and intensively at uniform time intervals, such as GPS data. The latter is less structured and record data in relatively time point intervals, including sensor-based, network-based and signal-based data (Kong et al. 2018).

-

In this course, we cover analytical and modelling approaches for point, lattice/areal and flow data. While we do not explicitly analyse trajectory data, various of the analytical approaches described in this book can be extended to incorporate time, and can be applied to model these types of data. In Chapter 10, we describe approaches to analyse and model spatio-temporal data. These same methods can be applied to trajectory data.

-
-
-

2.2 Hierarchical Structure of Data

+

In this course, we cover analytical and modelling approaches for point, lattice/areal and flow data. While we do not explicitly analyse trajectory data, various of the analytical approaches described in this book can be extended to incorporate time, and can be applied to model these types of data. In Chapter 10, we describe approaches to analyse and model spatio-temporal data. These same methods can be applied to trajectory data.

+

+2.2 Hierarchical Structure of Data

The hierarchical organisation is a key feature of spatial data. Smaller geographical units are organised within larger geographical units. You can find the hierarchical representation of UK Statistical Geographies on the Office for National Statistics website. In the bottom part of the output below, we can observe a spatial data frame for Liverpool displaying the hierarchical structure of census data (from the smallest to the largest): Output Areas (OAs), Lower Super Output Areas (LSOAs), Middle Super Output Areas (MSOAs) and Local Authority Districts (LADs). This hierarchical structure entails that units in smaller geographies are nested within units in larger geographies, and that smaller units can be aggregated to produce large units.

- -
-
Simple feature collection with 6 features and 4 fields
 Geometry type: MULTIPOLYGON
@@ -277,101 +315,92 @@ 

Simple Features.

-

-
-

2.3 Key Challenges

+

+2.3 Key Challenges

Major challenges exist when working with spatial data. Below we explore some of the key longstanding problems data scientists often face when working with geographical data.

-
-

2.3.1 Modifible Area Unit Problem (MAUP)

+

+2.3.1 Modifible Area Unit Problem (MAUP)

The Modifible Area Unit Problem (MAUP) represents a challenge that has troubled geographers for decades (Openshaw 1981). Two aspects of the MAUP are normally recognised in empirical analysis relating to scale and zonation. Fig. 2 illustrates these issues

  • Scale refers to the idea that a geographical area can be divided into geographies with differing numbers of spatial units.

  • Zonation refers to the idea that a geographical area can be divided into the same number of units in a variety of ways.

-
-

-

Fig. 2. MAUP effect. (a) scale effect; and, (b) zonation effect. Source: Loidl et al. (2016).

-
+

+
Fig. 2. MAUP effect. (a) scale effect; and, (b) zonation effect. Source: Loidl et al. (2016).

The MAUP is a critical issue as it can impact our analysis and thus any conclusions we can infer from our results (e.g. Fotheringham and Wong 1991). There is no agreed systematic approach on how to handle the effects of the MAUP. Some have suggested to perform analyses based on different existing geographical scales, and assess the consistency of the results and identify potential sources of change. The issue with such approach is that results from analysis at different scales are likely to differ because distinct dimensions of a geographic process may be captured at different scales. For example, in migration studies, smaller geographies may be more suitable to capture residential mobility over short distances, while large geographies may be more suitable to capture long-distance migration. And it is well documented that these types of moves are driven by different factors. While residential mobility tends to be driven by housing related reasons, long-distance migration is more closely related to employment-related motives (Niedomysl 2011).

-

An alternative approach is to use the smallest geographical system available and create random aggregations at various geographical scales, to directly quantify the extent of scale and zonation. This approach has shown promising results in applications to study internal migration flows (Stillwell, Daras, and Bell 2018). Another approach involves the production of “meaningful” or functional geographies that can more appropriately capture the process of interest. There is an active area of work defining functional labour markets (Casado-Díaz, Martínez-Bernabéu, and Rowe 2017), urban areas (Arribas-Bel, Garcia-López, and Viladecans-Marsal 2021) and various forms of geodemographic classifications (Singleton and Spielman 2013; Patias, Rowe, and Cavazzi 2019) . However there is the recognition that none of the existing approaches resolve the effects of the MAUP and recently it has been suggested that the most plausible ‘solution’ would be to ignore the MAUP (Wolf et al. 2020).

-
-
-

2.3.2 Ecological Fallacy

-

Ecological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is Robinson (1950) who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. Robinson (1950) used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was 0.53. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative -0.11 which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data.

-
-
-

2.3.3 Spatial Dependence

-

Spatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that these values are more similar (or less similar) than expected for randomly associated pairs of observations (Anselin 1988). For example, we could think of observed patterns of ethnic segregation in an area are a result of spillover effects of pre-existing patterns of ethnic segregation in neighbouring areas. Chapter 5 will illustrate approach to explicitly incorporate spatial dependence in regression analysis.

-
-
-

2.3.4 Spatial Heterogeneity

-

Spatial heterogeneity refers to the uneven distribution of a variable’s values across space. Concentration of deprivation or unemployment across an area are good examples of spatial heterogeneity. We illustrate various ways to visualise, explore and measure the spatial distribution of data in multiple chapters. We also discuss on potential modelling approaches to capture spatial heterogeneity in Chapter 5, Chapter 7 and Chapter 10.

-
-
-

2.3.5 Spatial nonstationarity

-

Spatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space. In a modelling context, it relates to a situation in which a simple “global” model is inappropriate to explain the relationships between a set of variables. The geographical nature of the model must be modified to reflect local structural relationships within the data. For example, ethinic segregation has been positively associated with employment outcomes in some countries pointing to networks in pre-existing communities facilitating access to the local labour market. Inversely ethinic segregation has been negatively associated with employment outcomes pointing to lack of integration into the broader local community. We illustrate various modelling approaches to capture spatial nonstationarity in Chapter 8 and Chapter 9.

+

An alternative approach is to use the smallest geographical system available and create random aggregations at various geographical scales, to directly quantify the extent of scale and zonation. This approach has shown promising results in applications to study internal migration flows (Stillwell, Daras, and Bell 2018). Another approach involves the production of “meaningful” or functional geographies that can more appropriately capture the process of interest. There is an active area of work defining functional labour markets (Casado-Díaz, Martínez-Bernabéu, and Rowe 2017), urban areas (Arribas-Bel, Garcia-López, and Viladecans-Marsal 2021) and various forms of geodemographic classifications (Singleton and Spielman 2013; Patias, Rowe, and Cavazzi 2019) . However there is the recognition that none of the existing approaches resolve the effects of the MAUP and recently it has been suggested that the most plausible ‘solution’ would be to ignore the MAUP (Wolf et al. 2020).

+

+2.3.2 Ecological Fallacy

+

Ecological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is Robinson (1950) who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. Robinson (1950) used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was 0.53. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative -0.11 which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data.

+

+2.3.3 Spatial Dependence

+

Spatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that these values are more similar (or less similar) than expected for randomly associated pairs of observations (Anselin 1988). For example, we could think of observed patterns of ethnic segregation in an area are a result of spillover effects of pre-existing patterns of ethnic segregation in neighbouring areas. Chapter 5 will illustrate approach to explicitly incorporate spatial dependence in regression analysis.

+

+2.3.4 Spatial Heterogeneity

+

Spatial heterogeneity refers to the uneven distribution of a variable’s values across space. Concentration of deprivation or unemployment across an area are good examples of spatial heterogeneity. We illustrate various ways to visualise, explore and measure the spatial distribution of data in multiple chapters. We also discuss on potential modelling approaches to capture spatial heterogeneity in Chapter 5, Chapter 7 and Chapter 10.

+

+2.3.5 Spatial nonstationarity

+

Spatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space. In a modelling context, it relates to a situation in which a simple “global” model is inappropriate to explain the relationships between a set of variables. The geographical nature of the model must be modified to reflect local structural relationships within the data. For example, ethinic segregation has been positively associated with employment outcomes in some countries pointing to networks in pre-existing communities facilitating access to the local labour market. Inversely ethinic segregation has been negatively associated with employment outcomes pointing to lack of integration into the broader local community. We illustrate various modelling approaches to capture spatial nonstationarity in Chapter 8 and Chapter 9.

-
-
- -
- -
+ \ No newline at end of file diff --git a/docs/03-data-wrangling.html b/docs/03-data-wrangling.html index f5bd32f..d7c8142 100644 --- a/docs/03-data-wrangling.html +++ b/docs/03-data-wrangling.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 3  Data Wrangling - +} @@ -122,14 +90,19 @@ - - - - - +} + + + + - - +
-
- -
- -
+
-
-
- -
-
-

3  Data Wrangling

+
+

3  Data Wrangling

@@ -323,10 +316,9 @@

1 introduces computational notebooks, basic functions and data types. These are all important concepts that we will use during the module.

-

If you are already familiar with R, R notebooks and data types, you may want to jump to Section Read Data and start from there. This section describes how to read and manipulate data using sf and tidyverse functions, including mutate(), %>% (known as pipe operator), select(), filter() and specific packages and functions how to manipulate spatial data.

+

In this chapter, we will cover the fundamentals of the concepts and functions that you will need to know to navigate this book. We will introduce key concepts and functions relating to what computational notebooks are and how they work. We will also cover basic R functions and data types, including the use of factors. Additionally, we will offer a basic understanding of the manipulation and mapping of spatial data frames using commonly used libraries such as tidyverse, sf, ggplot and tmap.

+

If you are already familiar with R, R computational notebooks and data types, you may want to jump to Section Read Data and start from there. This section describes how to read and manipulate data using sf and tidyverse functions, including mutate(), %>% (known as pipe operator), select(), filter() and specific packages and functions how to manipulate spatial data.

The chapter is based on:

  • Grolemund and Wickham (2019), this book illustrates key libraries, including tidyverse, and functions for data manipulation in R

  • @@ -334,59 +326,81 @@

    Williamson (2018), some examples from the first lecture of ENVS450 are used to explain the various types of random variables.

  • Lovelace, Nowosad, and Muenchow (2019), a really good book on handling spatial data and historical background of the evolution of R packages for spatial data analysis.

-
-

3.1 Dependencies

-

This tutorial uses the libraries below. Ensure they are installed on your machine2 before loading them executing the following code chunk:

-
-
# Data manipulation, transformation and visualisation
-library(tidyverse)
-# Nice tables
-library(kableExtra)
-# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
-library(sf) 
-# Spatial objects conversion
-library(sp) 
-# Thematic maps
-library(tmap) 
-# Colour palettes
-library(RColorBrewer) 
-# More colour palettes
-library(viridis)
-
-
-
-

3.2 Introducing R

-

R is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on CRAN. CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R.

+

+3.1 Dependencies

+

This chapter uses the libraries below. Ensure they are installed on your machine1 before you progress.

+

1 You can install package mypackage by running the command install.packages("mypackage") on the R prompt or through the Tools --> Install Packages... menu in RStudio.

+
# data manipulation, transformation and visualisation
+library(tidyverse)
+# nice tables
+library(kableExtra)
+# spatial data manipulation
+library(sf) 
+# thematic mapping
+library(tmap) 
+# colour palettes
+library(RColorBrewer) 
+library(viridis)
+
+

+3.2 Introducing R

+

R is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on The Comprehensive R Archive Network (CRAN). CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R.

Commands are sent to R using either the terminal / command line or the R Console which is installed with R on either Windows or OS X. On Linux, there is no equivalent of the console, however, third party solutions exist. On your own machine, R can be installed from here.

Normally RStudio is used to implement R coding. RStudio is an integrated development environment (IDE) for R and provides a more user-friendly front-end to R than the front-end provided with R.

To run R or RStudio, just double click on the R or RStudio icon. Throughout this module, we will be using RStudio:

-
-

-

Fig. 1. RStudio features.

-
+

+
Fig. 1. RStudio features.

If you would like to know more about the various features of RStudio, watch this video

-
-
-

3.3 Setting the working directory

-

Before we start any analysis, ensure to set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the data folder lives.

+

+3.3 Setting the working directory

+

Before we start any analysis, ensure to set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the data folder lives.

-
#setwd('../data/sar.csv')
-#setwd('.')
+
setwd('../data/sar.csv')
+setwd('.')
-

Note: It is good practice to not include spaces when naming folders and files. Use underscores or dots.

-

You can check your current working directory by typing:

+ +
+
+
+
+ +
+
+Note +
+
+
+

It is good practice to not include spaces when naming folders and files. Use underscores or dots.

+
+
+

You can check your current working directory by typing:

-
getwd()
+
-
[1] "/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san"
+
[1] "/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san"
-
-
-

3.4 R Scripts and Computational Notebooks

+

+3.4 R Scripts and Computational Notebooks

An R script is a series of commands that you can execute at one time and help you save time. So you do not repeat the same steps every time you want to execute the same process with different datasets. An R script is just a plain text file with R commands in it.

+ +
+
+
+
+ +
+
+Note +
+
+
+

To get familiar with good practices in writing your code in R, we recommend the Chapter Workflow: basics and Workflow: scripts and projects from the R in Data Science book by Wickham, Çetinkaya-Rundel, and Grolemund (2023).

+
+
+

https://r4ds.hadley.nz/workflow-basics.html

To create an R script in RStudio, you need to

  • Open a new script file: File > New File > R Script

  • @@ -394,7 +408,7 @@

    -
    mtcars
    +
    mtcars
                         mpg cyl  disp  hp drat    wt  qsec vs am gear carb
     Mazda RX4           21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
    @@ -432,18 +446,18 @@ 

    Xie, Allaire, and Grolemund (2019).

    +

    An R Notebook or a Quarto Document are a Markdown options with descriptive text and code chunks that can be executed independently and interactively, with output visible immediately beneath a code chunk - see Xie, Allaire, and Grolemund (2019). A Quarto Document is an improved version of the original R Notebook. Quarto Document requires a package called Quarto. Quarto does not have a dependency or requirement for R. Quarto is multilingual, beginning with R, Python, Javascript, and Julia. The concept is that Quarto will work even for languages that do not yet exist. This book was original written in R Notebook but later transitioned into Quarto Documents.

    To create an R Notebook, you need to:

      -
    • Open a new script file: File > New File > R Notebook
    • +
    • Open a new script file: File > New File > R Notebook +
    -
    -

    -

    Fig. 2. YAML metadata for notebooks.

    -
    +

    +
    Fig. 2. YAML metadata for notebooks.
    • Insert code chunks, either:
    • @@ -451,36 +465,35 @@

    • use the Insert command on the editor toolbar;
    • use the keyboard shortcut Ctrl + Alt + I or Cmd + Option + I (Mac); or,
    • -
    • type the chunk delimiters ```{r} and ```
    • +
    • type the chunk delimiters ```{r} and ``` +
    • -

      In a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets eg.

      +

      In a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets e.g.:

      -
      -

      -

      Fig. 3. Code chunk example. Details on the various options: https://rmarkdown.rstudio.com/lesson-3.html

      -
      +

      +
      Fig. 3. Code chunk example. Details on the various options: https://rmarkdown.rstudio.com/lesson-3.html
      • Execute code: hit “Run Current Chunk”, Ctrl + Shift + Enter or Cmd + Shift + Enter (Mac)

      • Save an R notebook: File > Save As. A notebook has a *.Rmd extension and when it is saved a *.nb.html file is automatically created. The latter is a self-contained HTML file which contains both a rendered copy of the notebook with all current chunk outputs and a copy of the *.Rmd file itself.

      Rstudio also offers a Preview option on the toolbar which can be used to create pdf, html and word versions of the notebook. To do this, choose from the drop-down list menu knit to ...

      -

      For this module, we will be using computational notebooks through Quarto; that is, Quarto Document. “Quarto is a multi-language, next generation version of R Markdown from RStudio, with many new new features and capabilities. Like R Markdown, Quarto uses Knitr to execute R code, and is therefore able to render most existing Rmd files without modification.”

      -

      To create a Quarto Document, you need to:

      +

      To create a Quarto Document, you need to:

        -
      • Open a new script file: File > New File > Quarto Document
      • +
      • Open a new script file: File > New File > Quarto Document +

      Quarto Documents work in the same way as R Notebooks with small variations. You find a comprehensive guide on the Quarto website.

      -

-
-

3.5 Getting Help

+

+3.5 Getting Help

You can use help or ? to ask for details for a specific function:

-
help(sqrt) #or ?sqrt
+
help(sqrt) #or
+?sqrt

And using example provides examples for said function:

-
-
example(sqrt)
+
+
example(sqrt)

 sqrt> require(stats) # for spline
@@ -492,10 +505,8 @@ 

-
-
-

-

Example sqrt

+
+

@@ -504,9 +515,8 @@

-
-
-

3.6 Variables and objects

+

+3.6 Variables and objects

An object is a data structure having attributes and methods. In fact, everything in R is an object!

A variable is a type of data object. Data objects also include list, vector, matrices and text.

    @@ -515,28 +525,28 @@

    In R a variable can be created by using the symbol <- to assign a value to a variable name. The variable name is entered on the left <- and the value on the right. Note: Data objects can be given any name, provided that they start with a letter of the alphabet, and include only letters of the alphabet, numbers and the characters . and _. Hence AgeGroup, Age_Group and Age.Group are all valid names for an R data object. Note also that R is case-sensitive, to agegroup and AgeGroup would be treated as different data objects.

    To save the value 28 to a variable (data object) labelled age, run the code:

    -
    age <- 28
    +
    age <- 28
    • Inspecting a data object

    To inspect the contents of the data object age run the following line of code:

    -
    age
    +
    age
    [1] 28

    Find out what kind (class) of data object age is using:

    -
    class(age) 
    +
    class(age) 
    [1] "numeric"

    Inspect the structure of the age data object:

    -
    str(age) 
    +
    str(age) 
     num 28
    @@ -544,32 +554,32 @@

  • The vector data object
-

What if we have more than one response? We can use the c( ) function to combine multiple values into one data vector object:

+

What if we have more than one response? We can use the c( ) function to combine multiple values into one data vector object:

-
age <- c(28, 36, 25, 24, 32)
-age
+
age <- c(28, 36, 25, 24, 32)
+age
[1] 28 36 25 24 32
-
class(age) #Still numeric..
+
class(age) #Still numeric..
[1] "numeric"
-
str(age) #..but now a vector (set) of 5 separate values
+
str(age) #..but now a vector (set) of 5 separate values
 num [1:5] 28 36 25 24 32

Note that on each line in the code above any text following the # character is ignored by R when executing the code. Instead, text following a # can be used to add comments to the code to make clear what the code is doing. Two marks of good code are a clear layout and clear commentary on the code.

-
-

3.6.1 Basic Data Types

+

+3.6.1 Basic Data Types

There are a number of data types. Four are the most common. In R, numeric is the default type for numbers. It stores all numbers as floating-point numbers (numbers with decimals). This is because most statistical calculations deal with numbers with up to two decimals.

  • Numeric
-
num <- 4.5 # Decimal values
-class(num)
+
num <- 4.5 # Decimal values
+class(num)
[1] "numeric"
@@ -578,8 +588,8 @@

Integer
-
int <- as.integer(4) # Natural numbers. Note integers are also numerics.
-class(int)
+
int <- as.integer(4) # Natural numbers. Note integers are also numerics.
+class(int)
[1] "integer"
@@ -588,8 +598,8 @@

Character
-
cha <- "are you enjoying this?" # text or string. You can also type `as.character("are you enjoying this?")`
-class(cha)
+
cha <- "are you enjoying this?" # text or string. You can also type `as.character("are you enjoying this?")`
+class(cha)
[1] "character"
@@ -598,145 +608,124 @@

Logical
-
log <- 2 < 1 # assigns TRUE or FALSE. In this case, FALSE as 2 is greater than 1
-log
+
log <- 2 < 1 # assigns TRUE or FALSE. In this case, FALSE as 2 is greater than 1
+log
[1] FALSE
-
class(log)
+
class(log)
[1] "logical"
-

-
-

3.6.2 Random Variables

+

+3.6.2 Random Variables

In statistics, we differentiate between data to capture:

    -
  • Qualitative attributes categorise objects eg.gender, marital status. To measure these attributes, we use Categorical data which can be divided into:

    +
  • +

    Qualitative attributes categorise objects eg.gender, marital status. To measure these attributes, we use Categorical data which can be divided into:

      -
    • Nominal data in categories that have no inherent order eg. gender
    • -
    • Ordinal data in categories that have an inherent order eg. income bands
    • -
  • -
  • Quantitative attributes:

    +
  • +Nominal data in categories that have no inherent order eg. gender
  • +
  • +Ordinal data in categories that have an inherent order eg. income bands
  • +
+ +
  • +

    Quantitative attributes:

      -
    • Discrete data: count objects of a certain category eg. number of kids, cars
    • -
    • Continuous data: precise numeric measures eg. weight, income, length.
    • -
  • +
  • +Discrete data: count objects of a certain category eg. number of kids, cars
  • +
  • +Continuous data: precise numeric measures eg. weight, income, length.
  • + +

    In R these three types of random variables are represented by the following types of R data object:

    -
    +
    - - - - - - - - +
    -variables - -objects -
    + + + + - - - + + + - - - + + + - - - + + + - - - + + + -
    variablesobjects
    -nominal - -factor -
    nominalfactor
    -ordinal - -ordered factor -
    ordinalordered factor
    -discrete - -numeric -
    discretenumeric
    -continuous - -numeric -
    continuousnumeric
    -

    Survey and R data types

    We have already encountered the R data type numeric. The next section introduces the factor data type.

    -
    -

    3.6.2.1 Factor

    +

    +3.6.2.1 Factor

    What is a factor?

    -

    A factor variable assigns a numeric code to each possible category (level) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of 10,000 males and females to a list of 10,000 1s and 0s. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes.

    -

    For example, the variable gender, converted to a factor, would be stored as a series of 1s and 2s, where 1 = female and 2 = male; but would be displayed in all outputs using their category labels of female and male.

    +

    A factor variable assigns a numeric code to each possible category (level) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of 10,000 males and females to a list of 10,000 1s and 0s. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes.

    +

    For example, the variable gender, converted to a factor, would be stored as a series of 1s and 2s, where 1 = female and 2 = male; but would be displayed in all outputs using their category labels of female and male.

    Creating a factor

    -

    To convert a numeric or character vector into a factor use the factor( ) function. For instance:

    +

    To convert a numeric or character vector into a factor use the factor( ) function. For instance:

    -
    gender <- c("female","male","male","female","female") # create a gender variable
    -gender <- factor(gender) # replace character vector with a factor version
    -gender
    +
    gender <- c("female","male","male","female","female") # create a gender variable
    +gender <- factor(gender) # replace character vector with a factor version
    +gender
    [1] female male   male   female female
     Levels: female male
    -
    class(gender)
    +
    class(gender)
    [1] "factor"
    -
    str(gender)
    +
    str(gender)
     Factor w/ 2 levels "female","male": 1 2 2 1 1
    -

    Now gender is a factor and is stored as a series of 1s and 2s, with 1s representing females and 2s representing males. The function levels( ) lists the levels (categories) associated with a given factor variable:

    +

    Now gender is a factor and is stored as a series of 1s and 2s, with 1s representing females and 2s representing males. The function levels( ) lists the levels (categories) associated with a given factor variable:

    -
    levels(gender)
    +
    levels(gender)
    [1] "female" "male"  
    -

    The categories are reported in the order that they have been numbered (starting from 1). Hence from the output we can infer that females are coded as 1, and males as 2.

    -
    -
    -
    -
    -

    3.7 Data Frames

    +

    The categories are reported in the order that they have been numbered (starting from 1). Hence from the output we can infer that females are coded as 1, and males as 2.

    +

    +3.7 Data Frames

    R stores different types of data using different types of data structure. Data are normally stored as a data.frame. A data frames contain one row per observation (e.g. wards) and one column per attribute (eg. population and health).

    We create three variables wards, population (pop) and people with good health (ghealth). We use 2011 census data counts for total population and good health for wards in Liverpool.

    -
    wards <- c("Allerton and Hunts Cross","Anfield","Belle Vale","Central","Childwall","Church","Clubmoor","County","Cressington","Croxteth","Everton","Fazakerley","Greenbank","Kensington and Fairfield","Kirkdale","Knotty Ash","Mossley Hill","Norris Green","Old Swan","Picton","Princes Park","Riverside","St Michael's","Speke-Garston","Tuebrook and Stoneycroft","Warbreck","Wavertree","West Derby","Woolton","Yew Tree")
    -
    -pop <- c(14853,14510,15004,20340,13908,13974,15272,14045,14503,
    -                14561,14782,16786,16132,15377,16115,13312,13816,15047,
    -                16461,17009,17104,18422,12991,20300,16489,16481,14772,
    -                14382,12921,16746)
    -
    -ghealth <- c(7274,6124,6129,11925,7219,7461,6403,5930,7094,6992,
    -                 5517,7879,8990,6495,6662,5981,7322,6529,7192,7953,
    -                 7636,9001,6450,8973,7302,7521,7268,7013,6025,7717)
    +
    wards <- c("Allerton and Hunts Cross","Anfield","Belle Vale","Central","Childwall","Church","Clubmoor","County","Cressington","Croxteth","Everton","Fazakerley","Greenbank","Kensington and Fairfield","Kirkdale","Knotty Ash","Mossley Hill","Norris Green","Old Swan","Picton","Princes Park","Riverside","St Michael's","Speke-Garston","Tuebrook and Stoneycroft","Warbreck","Wavertree","West Derby","Woolton","Yew Tree")
    +
    +pop <- c(14853,14510,15004,20340,13908,13974,15272,14045,14503,
    +                14561,14782,16786,16132,15377,16115,13312,13816,15047,
    +                16461,17009,17104,18422,12991,20300,16489,16481,14772,
    +                14382,12921,16746)
    +
    +ghealth <- c(7274,6124,6129,11925,7219,7461,6403,5930,7094,6992,
    +                 5517,7879,8990,6495,6662,5981,7322,6529,7192,7953,
    +                 7636,9001,6450,8973,7302,7521,7268,7013,6025,7717)

    Note that pop and ghealth and wards contains characters.

    -
    -

    3.7.1 Creating A Data Frame

    +

    +3.7.1 Creating A Data Frame

    We can create a data frame and examine its structure:

    -
    df <- data.frame(wards, pop, ghealth)
    -df # or use view(data)
    +
    df <- data.frame(wards, pop, ghealth)
    +df # or use view(data)
                          wards   pop ghealth
     1  Allerton and Hunts Cross 14853    7274
    @@ -770,7 +759,7 @@ 

    29 Woolton 12921 6025 30 Yew Tree 16746 7717

    -
    str(df) # or use glimpse(data) 
    +
    str(df) # or use glimpse(data) 
    'data.frame':   30 obs. of  3 variables:
      $ wards  : chr  "Allerton and Hunts Cross" "Anfield" "Belle Vale" "Central" ...
    @@ -778,22 +767,21 @@ 

    $ ghealth: num 7274 6124 6129 11925 7219 ...

    -
    -
    -

    3.7.2 Referencing Data Frames

    -

    Throughout this module, you will need to refer to particular parts of a dataframe - perhaps a particular column (an area attribute); or a particular subset of respondents. Hence it is worth spending some time now mastering this particular skill.

    +

    +3.7.2 Referencing Data Frames

    +

    To refer to particular parts of a dataframe - say, a particular column (an area attribute), or a subset of respondents. Hence it is worth spending some time understanding how to reference dataframes.

    The relevant R function, [ ], has the format [row,col] or, more generally, [set of rows, set of cols].

    Run the following commands to get a feel of how to extract different slices of the data:

    -
    df # whole data.frame
    -df[1, 1] # contents of first row and column
    -df[2, 2:3] # contents of the second row, second and third columns
    -df[1, ] # first row, ALL columns [the default if no columns specified]
    -df[ ,1:2] # ALL rows; first and second columns
    -df[c(1,3,5), ] # rows 1,3,5; ALL columns
    -df[ , 2] # ALL rows; second column (by default results containing only 
    -             #one column are converted back into a vector)
    -df[ , 2, drop=FALSE] # ALL rows; second column (returned as a data.frame)
    +
    df # whole data.frame
    +df[1, 1] # contents of first row and column
    +df[2, 2:3] # contents of the second row, second and third columns
    +df[1, ] # first row, ALL columns [the default if no columns specified]
    +df[ ,1:2] # ALL rows; first and second columns
    +df[c(1,3,5), ] # rows 1,3,5; ALL columns
    +df[ , 2] # ALL rows; second column (by default results containing only 
    +             #one column are converted back into a vector)
    +df[ , 2, drop=FALSE] # ALL rows; second column (returned as a data.frame)

    In the above, note that we have used two other R functions:

      @@ -803,28 +791,41 @@

      -
      df[,"pop"] # variable name in quotes inside the square brackets
      -df$pop # variable name prefixed with $ and appended to the data.frame name
      -# or you can use attach
      -attach(df)
      -pop # but be careful if you already have an age variable in your local workspace
      +
      df[,"pop"] # variable name in quotes inside the square brackets
      +df$pop # variable name prefixed with $ and appended to the data.frame name
      +# or you can use attach
      +attach(df)
      +pop # but be careful if you already have an age variable in your local workspace

    -

    Want to check the variables available, use the names( ):

    +

    Want to check the variables available, use the names( ):

    -
    names(df)
    +
    names(df)
    [1] "wards"   "pop"     "ghealth"
    - - -
    -

    3.8 Read Data

    +

    +3.8 Read Data

    Ensure your memory is clear

    -
    rm(list=ls()) # rm for targeted deletion / ls for listing all existing objects
    +
    rm(list=ls()) # rm for targeted deletion / ls for listing all existing objects
    +
    + +
    +
    +
    +
    + +
    +
    +Note +
    +
    +
    +

    When opening a file, ensure the correct directory set up pointing to your data. It may differ from your existing working directory.

    -

    There are many commands to read / load data onto R. The command to use will depend upon the format they have been saved. Normally they are saved in csv format from Excel or other software packages. So we use either:

    +
    +

    There are many commands to read / load data onto R. The command to use will depend upon the format they have been saved. Normally they are saved in csv format from Excel or other software packages. So we use either:

    • df <- read.table("path/file_name.csv", header = FALSE, sep =",")
    • df <- read("path/file_name.csv", header = FALSE)
    • @@ -832,8 +833,8 @@

      To read files in other formats, refer to this useful DataCamp tutorial

      -
      census <- read.csv("data/census/census_data.csv")
      -head(census)
      +
      census <- read.csv("data/census/census_data.csv")
      +head(census)
             code                     ward pop16_74 higher_managerial   pop ghealth
       1 E05000886 Allerton and Hunts Cross    10930              1103 14853    7274
      @@ -843,25 +844,19 @@ 

      -
      # NOTE: always ensure your are setting the correct directory leading to the data. 
      -# It may differ from your existing working directory
      -
      -

      3.8.1 Quickly inspect the data

      -
        -
      1. What class?

      2. -
      3. What R data types?

      4. -
      5. What data types?

      6. -
      +

      +3.8.1 Quickly inspect the data

      +

      Using the following questions to lead the inspection: What class? What R data types? What data types?

      -
      # 1
      -class(census)
      -# 2 & 3
      -str(census)
      +
      # 1
      +class(census)
      +# 2 & 3
      +str(census)

      Just interested in the variable names:

      -
      names(census)
      +
      names(census)
      [1] "code"              "ward"              "pop16_74"         
       [4] "higher_managerial" "pop"               "ghealth"          
      @@ -869,43 +864,41 @@

      -

      3.9 Manipulation Data

      -
      -

      3.9.1 Adding New Variables

      +

      +3.9 Manipulation Data

      +

      +3.9.1 Adding New Variables

      Usually you want to add / create new variables to your data frame using existing variables eg. computing percentages by dividing two variables. There are many ways in which you can do this i.e. referecing a data frame as we have done above, or using $ (e.g. census$pop). For this module, we’ll use tidyverse:

      -
      census <- census %>% mutate(per_ghealth = ghealth / pop)
      +
      census <- census %>% 
      +  mutate( per_ghealth = ghealth / pop )

      Note we used a pipe operator %>%, which helps make the code more efficient and readable - more details, see Grolemund and Wickham (2019). When using the pipe operator, recall to first indicate the data frame before %>%.

      Note also the use a variable name before the = sign in brackets to indicate the name of the new variable after mutate.

      -
      -
      -

      3.9.2 Selecting Variables

      +

      +3.9.2 Selecting Variables

      Usually you want to select a subset of variables for your analysis as storing to large data sets in your R memory can reduce the processing speed of your machine. A selection of data can be achieved by using the select function:

      -
      ndf <- census %>% select(ward, pop16_74, per_ghealth)
      +
      ndf <- census %>% 
      +  select( ward, pop16_74, per_ghealth )

      Again first indicate the data frame and then the variable you want to select to build a new data frame. Note the code chunk above has created a new data frame called ndf. Explore it.

      -
      -
      -

      3.9.3 Filtering Data

      +

      +3.9.3 Filtering Data

      You may also want to filter values based on defined conditions. You may want to filter observations greater than a certain threshold or only areas within a certain region. For example, you may want to select areas with a percentage of good health population over 50%:

      -
      ndf2 <- census %>% filter(per_ghealth < 0.5)
      +
      ndf2 <- census %>% 
      +  filter( per_ghealth < 0.5 )

      You can use more than one variables to set conditions. Use “,” to add a condition.

      -
      -
      -

      3.9.4 Joining Data Drames

      -

      When working with spatial data, we often need to join data. To this end, you need a common unique id variable. Let’s say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (ie. census_data2.csv).

      +

      +3.9.4 Joining Data Drames

      +

      When working with spatial data, we often need to join data. To this end, you need a common unique id variable. Let’s say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (i.e. census_data2.csv).

      -
      # read data
      -census2 <- read.csv("data/census/census_data2.csv")
      -# visualise data structure
      -str(census2)
      +
      # read data
      +census2 <- read.csv("data/census/census_data2.csv")
      +# visualise data structure
      +str(census2)
      'data.frame':   30 obs. of  3 variables:
        $ geo_code               : chr  "E05000886" "E05000887" "E05000888" "E05000889" ...
      @@ -913,12 +906,15 @@ 

      -

      The variable geo_code in this data frame corresponds to the code in the existing data frame and they are unique so they can be automatically matched by using the merge() function. The merge() function uses two arguments: x and y. The former refers to data frame 1 and the latter to data frame 2. Both of these two data frames must have a id variable containing the same information. Note they can have different names. Another key argument to include is all.x=TRUE which tells the function to keep all the records in x, but only those in y that match in case there are discrepancies in the id variable.

      +

      The variable geo_code in this data frame corresponds to the code in the existing data frame and they are unique so they can be automatically matched by using the merge() function. The merge() function uses two arguments: x and y. The former refers to data frame 1 and the latter to data frame 2. Both of these two data frames must have a id variable containing the same information. Note they can have different names. Another key argument to include is all.x=TRUE which tells the function to keep all the records in x, but only those in y that match in case there are discrepancies in the id variable.

      -
      # join data frames
      -join_dfs <- merge(census, census2, by.x="code", by.y="geo_code", all.x = TRUE)
      -# check data
      -head(join_dfs)
      +
      # join data frames
      +join_dfs <- merge( census, # df1
      +                   census2, # df2
      +                   by.x="code", by.y="geo_code", # common ids
      +                   all.x = TRUE)
      +# check data
      +head(join_dfs)
             code                     ward pop16_74 higher_managerial   pop ghealth
       1 E05000886 Allerton and Hunts Cross    10930              1103 14853    7274
      @@ -936,35 +932,46 @@ 

      -
      -
      -

      3.9.5 Saving Data

      -

      It may also be convinient to save your R projects. They contains all the objects that you have created in your workspace by using the save.image( ) function:

      +

      +3.9.5 Saving Data

      +

      It may also be convenient to save your R projects. They contains all the objects that you have created in your workspace by using the save.image( ) function:

      -
      save.image("week1_envs453.RData")
      +
      save.image("week1_envs453.RData")
      -

      This creates a file labelled “week1_envs453.RData” in your working directory. You can load this at a later stage using the load( ) function.

      +

      This creates a file labelled “week1_envs453.RData” in your working directory. You can load this at a later stage using the load( ) function.

      -
      load("week1_envs453.RData")
      +
      load("week1_envs453.RData")

      Alternatively you can save / export your data into a csv file. The first argument in the function is the object name, and the second: the name of the csv we want to create.

      -
      write.csv(join_dfs, "join_censusdfs.csv")
      -
      -
      -
      -
      -

      3.10 Using Spatial Data Frames

      -

      A core area of this module is learning to work with spatial data in R. R has various purposedly designed packages for manipulation of spatial data and spatial analysis techniques. Various R packages exist in CRAN eg. spatial, sgeostat, splancs, maptools, tmap, rgdal, spand and more recent development of sf - see Lovelace, Nowosad, and Muenchow (2019) for a great description and historical context for some of these packages.

      -

      During this session, we will use sf.

      -

      We first need to import our spatial data. We will use a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data.

      -
      -

      3.10.1 Read Spatial Data

      +
      write.csv(join_dfs, "join_censusdfs.csv")
      +

    +

    +3.10 Using Spatial Data Frames

    +

    A core area of the module is learning to work with spatial data in R. R has various purposedly designed packages for manipulation of spatial data and spatial analysis techniques. Various packages exist in CRAN, including sf (Pebesma 2018, 2022a), stars (Pebesma 2022b), terra, s2 (Dunnington, Pebesma, and Rubak 2023), lwgeom (Pebesma 2023), gstat (Pebesma 2004; Pebesma and Graeler 2022), spdep (Bivand 2022), spatialreg (Bivand and Piras 2022), spatstat (Baddeley, Rubak, and Turner 2015; Baddeley, Turner, and Rubak 2022), tmap (Tennekes 2018, 2022), mapview (Appelhans et al. 2022) and more. A key package is this ecosystem is sf (Pebesma and Bivand 2023). R package sf provides a table format for simple features, where feature geometries are stored in a list-column. It appeared in 2016 and was developed to move spatial data analysis in R closer to standards-based approaches seen in the industry and open source projects, to build upon more modern versions of open source geospatial software stack and allow for integration of R spatial software with the tidyverse (Wickham et al. 2019), particularly ggplot2, dplyr, and tidyr. Hence, this book relies heavely on sf for the manipulation and analysis of the data.

    + +
    +
    +
    +
    + +
    +
    +Note +
    +
    +
    +

    Lovelace, Nowosad, and Muenchow (2024) provide a helpful overview and evolution of R spatial package ecosystem.

    +
    +
    +

    To read our spatial data, we use the st_read function. We read a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data.

    +

    +3.10.1 Read Spatial Data

    -
    oa_shp <- st_read("data/census/Liverpool_OA.shp")
    +
    oa_shp <- st_read("data/census/Liverpool_OA.shp")
    Reading layer `Liverpool_OA' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/census/Liverpool_OA.shp' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/census/Liverpool_OA.shp' 
       using driver `ESRI Shapefile'
     Simple feature collection with 1584 features and 18 fields
     Geometry type: MULTIPOLYGON
    @@ -975,20 +982,20 @@ 

    Examine the input data. A spatial data frame stores a range of attributes derived from a shapefile including the geometry of features (e.g. polygon shape and location), attributes for each feature (stored in the .dbf), projection and coordinates of the shapefile’s bounding box - for details, execute:

    -
    ?st_read
    +
    ?st_read

    You can employ the usual functions to visualise the content of the created data frame:

    -
    # visualise variable names
    -names(oa_shp)
    +
    # visualise variable names
    +names(oa_shp)
     [1] "OA_CD"    "LSOA_CD"  "MSOA_CD"  "LAD_CD"   "pop"      "H_Vbad"  
      [7] "H_bad"    "H_fair"   "H_good"   "H_Vgood"  "age_men"  "age_med" 
     [13] "age_60"   "S_Rent"   "Ethnic"   "illness"  "unemp"    "males"   
     [19] "geometry"
    -
    # data structure
    -str(oa_shp)
    +
    # data structure
    +str(oa_shp)
    Classes 'sf' and 'data.frame':  1584 obs. of  19 variables:
      $ OA_CD   : chr  "E00176737" "E00033515" "E00033141" "E00176757" ...
    @@ -1017,8 +1024,8 @@ 

    -
    # see first few observations
    -head(oa_shp)
    +
    # see first few observations
    +head(oa_shp)
    Simple feature collection with 6 features and 18 fields
     Geometry type: MULTIPOLYGON
    @@ -1048,97 +1055,216 @@ 

    -

    TASK:

    + +
    +
    +
    +
    + +
    +
    +

    Task

      -
    • What are the geographical hierarchy in these data?
    • -
    • What is the smallest geography?
    • -
    • What is the largest geography?
    • +
    • What are the geographical hierarchy in these data?
      +
    • +
    • What is the smallest geography?
      +
    • +
    • What is the largest geography?
      +
    -

    -
    -

    3.10.2 Basic Mapping

    -

    Again, many functions exist in CRAN for creating maps:

    +
    +
    +
    +

    +3.10.2 Basic Mapping

    +

    Many functions exist in CRAN for creating maps:

      -
    • plot to create static maps
    • -
    • tmap to create static and interactive maps
    • -
    • leaflet to create interactive maps
    • -
    • mapview to create interactive maps
    • -
    • ggplot2 to create data visualisations, including static maps
    • -
    • shiny to create web applications, including maps
    • +
    • +plot to create static maps
    • +
    • +tmap to create static and interactive maps
    • +
    • +leaflet to create interactive maps
    • +
    • +mapview to create interactive maps
    • +
    • +ggplot2 to create data visualisations, including static maps
    • +
    • +shiny to create web applications, including maps
    -

    Here this notebook demonstrates the use of plot and tmap. First plot is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right,

    -
    -

    3.10.2.1 Using plot

    -
    -
    # mapping geometry
    -plot(st_geometry(oa_shp))
    +

    In this book, we will make use of plot, tmap and ggplot. Normally you use plot to get a quick inspection of the data and tmap and ggplot to get publication quality data visualisations. First plot is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right.

    +

    Using plot

    +

    We can use the base plot function to display the boundaries of OAs in Liverpool.

    +
    +
    # mapping geometry
    +plot(st_geometry(oa_shp))
    -
    -
    -

    -

    OAs of Livepool

    +
    +

    -

    and then:

    +

    To visualise a column in the spatial data frame, we can run:

    -
    # map attributes, adding intervals
    -plot(oa_shp["Ethnic"], key.pos = 4, axes = TRUE, key.width = lcm(1.3), key.length = 1.,
    -     breaks = "jenks", lwd = 0.1, border = 'grey') 
    +
    # map attributes, adding intervals
    +plot(oa_shp["Ethnic"], # variable to visualise
    +     key.pos = 4, 
    +     axes = TRUE, 
    +     key.width = lcm(1.3), 
    +     key.length = 1.,
    +     breaks = "jenks", # algorithm to categorise the data
    +     lwd = 0.1, 
    +     border = 'grey') # boundary colour
    -
    -
    -

    -

    Spatial distribution of ethnic groups, Liverpool

    +
    +

    -

    TASK:

    + +
    +
    +
    +
    + +
    +
    +

    Task

    +

    What is the key pattern emerging from this map?

    +
    +
    +
    +

    Let us now explore ggplot or tmap.

    +

    Using ggplot

    +

    We can visualise spatial data frames using ggplot fairly easily. ggplot is a generic sets of functions which was not specifically designed for spatial mapping, but it is fairly flexible that allows producing great spatial data visualisations.

    +

    Following the grammar of ggplot, we plot spatial data drawing layers. ggplot has a basic structure of three components:

      -
    • What is the key pattern emerging from this map?
    • +
    • The data i.e. ggplot( data = *data frame*).
    • +
    • Geometries i.e. geom_xxx( ).
    • +
    • Aesthetic mapping i.e. aes(x=*variable*, y=*variable*) +
    -
    -
    -

    3.10.2.2 Using tmap

    -

    Similar to ggplot2, tmap is based on the idea of a ‘grammar of graphics’ which involves a separation between the input data and aesthetics (i.e. the way data are visualised). Each data set can be mapped in various different ways, including location as defined by its geometry, colour and other features. The basic building block is tm_shape() (which defines input data), followed by one or more layer elements such as tm_fill() and tm_dots().

    +

    We can put these three components together using +. This is similar to the ways we apply the pipe operator in for tidyverse. The latter component of aesthetic mapping can be added in both the ggplot and geom_xxx functions.

    +

    To map our data, we can then run:

    -
    # ensure geometry is valid
    -oa_shp = sf::st_make_valid(oa_shp)
    -
    -# map
    -legend_title = expression("% ethnic pop.")
    -map_oa = tm_shape(oa_shp) +
    -  tm_fill(col = "Ethnic", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .01)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) # add scale bar
    -map_oa
    +
    ggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map
    +  geom_sf(colour = "gray60",  # colour line
    +          size = 0.1) # line size
    -

    +
    +

    +
    -

    Note that the operation + is used to add new layers. You can set style themes by tm_style. To visualise the existing styles use tmap_style_catalogue(), and you can also evaluate the code chunk below if you would like to create an interactive map.

    +
    +

    We can change the colour palette by using a different colour palette. We can use the viridis package. The power of viridis is that it uses color scales that visually pleasing, colorblind friendly, print well in gray scale, and can be used for both categorical and continuous data. For categorical data you can also use ColourBrewer.

    +

    So let us (1) change the colour pallete to viridis, (2) remove the colour of the boundaries, and (3) replace the theme with the theme we will be using for the book. Let’s read the theme first and then implement these changes.

    +

    The ggplot themes for the book are in a file called data-visualisation_theme.R in the style folder. We read this file by running:

    -
    tmap_mode("view")
    -map_oa
    +
    source("./style/data-visualisation_theme.R")
    +
    +
    Loading required package: sysfonts
    -

    TASK:

    -
      -
    • Try mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate?
    • -
    -
    -
    -
    -

    3.10.3 Comparing geographies

    -

    If you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see lecture notes. To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together.

    +
    +
    Loading required package: showtextdb
    +
    + +

    We can now implement our changes:

    -
    # read data at the msoa level
    -msoa_shp <- st_read("data/census/Liverpool_MSOA.shp")
    +
    ggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map
    +  geom_sf(colour = "transparent") + # colour line
    +  scale_fill_viridis( option = "viridis" ) + # add viridis colour scheme
    +  theme_map_tufte()
    +
    +
    +

    +
    +
    +
    +
    +

    To master ggplot, see Wickham (2009).

    +

    Using tmap

    +

    Similar to ggplot2, tmap is based on the idea of a ‘grammar of graphics’ which involves a separation between the input data and aesthetics (i.e. the way data are visualised). Each data set can be mapped in various different ways, including location as defined by its geometry, colour and other features. The basic building block is tm_shape() (which defines input data), followed by one or more layer elements such as tm_fill() and tm_dots().

    +
    +
    # ensure geometry is valid
    +oa_shp = sf::st_make_valid(oa_shp)
    +
    +# map
    +legend_title = expression("% ethnic pop.")
    +map_oa = tm_shape(oa_shp) +
    +  tm_fill(col = "Ethnic", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .01)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) # add scale bar
    +map_oa
    +
    +
    +

    +
    +
    +
    +
    +

    Note that the operation +, as for ggplot is used to add new layers. You can set style themes by tm_style. To visualise the existing styles use tmap_style_catalogue(). An advantage of tmap is that you can easily create an interactive map by running tmap_mode.

    +
    +
    tmap_mode("view")
    +
    +
    tmap mode set to interactive viewing
    +
    +
    map_oa
    +
    +
    Compass not supported in view mode.
    +
    +
    +
    Warning: In view mode, scale bar breaks are ignored.
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    +

    Task

    +

    Try mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate?

    +
    +
    +
    +

    +3.10.3 Comparing geographies

    +

    If you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see (spatial_data?). To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together.

    + +
    +
    +
    +
    + +
    +
    +Note +
    +
    +
    +

    The first line of the chunk code include tmap_mode("plot") which tells R that we want a static map. tmap_mode works like a switch to interactive and non-interactive mapping.

    +
    +
    +
    +
    tmap_mode("plot")
    +
    +
    tmap mode set to plotting
    +
    +
    # read data at the msoa level
    +msoa_shp <- st_read("data/census/Liverpool_MSOA.shp")
    Reading layer `Liverpool_MSOA' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/census/Liverpool_MSOA.shp' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/census/Liverpool_MSOA.shp' 
       using driver `ESRI Shapefile'
     Simple feature collection with 61 features and 16 fields
     Geometry type: MULTIPOLYGON
    @@ -1146,116 +1272,115 @@ 

    -
    # ensure geometry is valid
    -msoa_shp = sf::st_make_valid(msoa_shp)
    -
    -# create a map
    -map_msoa = tm_shape(msoa_shp) +
    -  tm_fill(col = "Ethnic", title = legend_title, palette = magma(256), style = "cont") + 
    -  tm_borders(col = "white", lwd = .01)  + 
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    -
    -# arrange maps 
    -tmap_arrange(map_msoa, map_oa) 
    +
    # ensure geometry is valid
    +msoa_shp = sf::st_make_valid(msoa_shp)
    +
    +# create a map
    +map_msoa = tm_shape(msoa_shp) +
    +  tm_fill(col = "Ethnic", title = legend_title, palette = magma(256), style = "cont") + 
    +  tm_borders(col = "white", lwd = .01)  + 
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    +
    +# arrange maps 
    +tmap_arrange(map_msoa, map_oa) 
    -

    +
    +

    +
    -

    TASK:

    -
      -
    • What differences do you see between OAs and MSOAs?
    • -
    • Can you identify areas of spatial clustering? Where are they?
    • -
    -
    - -
    -

    3.11 Useful Functions

    - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    FunctionDescription
    read.csv()read csv files into data frames
    str()inspect data structure
    mutate()create a new variable
    filter()filter observations based on variable values
    %>%pipe operator - chain operations
    select()select variables
    merge()join dat frames
    st_readread spatial data (ie. shapefiles)
    plot()create a map based a spatial data set
    tm_shape(), tm_fill(), tm_borders()create a map using tmap functions
    tm_arrangedisplay multiple maps in a single “metaplot”
    + + + + -
    -
    -
    -
      -
    1. This chapter is part of Spatial Analysis Notes Creative Commons License
      Introduction – R Notebooks + Basic Functions + Data Types by Francisco Rowe is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.↩︎

    2. -
    3. You can install package mypackage by running the command install.packages("mypackage") on the R prompt or through the Tools --> Install Packages... menu in RStudio.↩︎

    4. -
    -
    - - - - + \ No newline at end of file diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-40-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-40-1.png deleted file mode 100644 index 0712bf9..0000000 Binary files a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-40-1.png and /dev/null differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-41-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-41-1.png index 5170fb6..53f0aa8 100644 Binary files a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-41-1.png and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-41-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-42-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-42-1.png index 4e54d01..5b8a062 100644 Binary files a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-42-1.png and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-42-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-43-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-43-1.png new file mode 100644 index 0000000..26138dd Binary files /dev/null and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-43-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-44-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-44-1.png deleted file mode 100644 index 07b9669..0000000 Binary files a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-44-1.png and /dev/null differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-45-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-45-1.png new file mode 100644 index 0000000..c1c3645 Binary files /dev/null and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-45-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-46-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-46-1.png new file mode 100644 index 0000000..034c672 Binary files /dev/null and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-46-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-48-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-48-1.png new file mode 100644 index 0000000..8108706 Binary files /dev/null and b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-48-1.png differ diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-5-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-6-1.png similarity index 100% rename from docs/03-data-wrangling_files/figure-html/unnamed-chunk-5-1.png rename to docs/03-data-wrangling_files/figure-html/unnamed-chunk-6-1.png diff --git a/docs/03-data-wrangling_files/figure-html/unnamed-chunk-7-1.png b/docs/03-data-wrangling_files/figure-html/unnamed-chunk-8-1.png similarity index 100% rename from docs/03-data-wrangling_files/figure-html/unnamed-chunk-7-1.png rename to docs/03-data-wrangling_files/figure-html/unnamed-chunk-8-1.png diff --git a/docs/04-points.html b/docs/04-points.html index e3d393f..c49a0eb 100644 --- a/docs/04-points.html +++ b/docs/04-points.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 4  Point Data Analysis - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    4  Point Data Analysis

    +
    +

    4  Point Data Analysis

    @@ -305,46 +314,42 @@

    offer a great introduction. +
  • +Lovelace, Nowosad, and Muenchow (2019) offer a great introduction.
  • Chapter 6 of Brunsdon and Comber (2015), in particular subsections 6.3 and 6.7.
  • -
  • Bivand, Pebesma, and Gómez-Rubio (2013) provides an in-depth treatment of spatial data in R.
  • +
  • +Bivand, Pebesma, and Gómez-Rubio (2013) provides an in-depth treatment of spatial data in R.
  • -
    -

    4.1 Dependencies

    -

    We will rely on the following libraries in this section, all of them included in Section 1.4.1:

    +

    +4.1 Dependencies

    +

    We will rely on the following libraries in this section, all of them included in Section 1.4.1:

    -
    # For pretty table
    -library(knitr)
    -# All things geodata
    -library(sf)
    -library(sp)
    -# Pretty graphics
    -library(ggplot2)
    -library(gridExtra)
    -# Thematic maps
    -library(tmap)
    -# Pretty maps
    -library(ggmap)
    -# For all your interpolation needs
    -library(gstat)
    -# For data manipulation
    -library(plyr)
    -
    -

    Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the house_transactions folder with the data lives.

    -
    -
    -

    4.2 Data

    +
    # data manipulation, transformation and visualisation
    +library(tidyverse)
    +# spatial data manipulation
    +library(sf)
    +library(sp)
    +# data visualisation
    +library(gridExtra)
    +# basemap
    +library(basemapR)
    +# interpolation
    +library(gstat)
    +library(hexbin)
    +

    +

    Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the house_transactions folder with the data lives.

    +

    +4.2 Data

    For this session, we will use the set of Airbnb properties for San Diego (US), borrowed from the “Geographic Data Science with Python” book (see here for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region.

    Let us start by reading the data, which comes in a GeoJSON:

    -
    db <- st_read("data/abb_sd/regression_db.geojson")
    +
    db <- st_read("data/abb_sd/regression_db.geojson")
    Reading layer `regression_db' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/abb_sd/regression_db.geojson' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/abb_sd/regression_db.geojson' 
       using driver `GeoJSON'
     Simple feature collection with 6110 features and 19 fields
     Geometry type: POINT
    @@ -355,7 +360,7 @@ 

    -
    colnames(db)
    +
     [1] "accommodates"       "bathrooms"          "bedrooms"          
      [4] "beds"               "neighborhood"       "pool"              
    @@ -368,190 +373,167 @@ 

    This basically shows there is a lot of values concentrated around the lower end of the distribution but a few very large ones. A usual transformation to shrink these differences is to take logarithms. The original table already contains an additional column with the logarithm of each price (log_price).

    -
    -
    # Create the histogram
    -hist <- qplot(data=db, x=log_price)
    -hist
    +
    +
    # Create the histogram
    +qplot( data = db, x = log_price )
    `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
    -
    -
    -

    -

    Log of AirBnb price in San Diego

    +
    +

    To obtain the spatial distribution of these houses, we need to focus on the geometry column. The easiest, quickest (and also “dirtiest”) way to get a sense of what the data look like over space is using plot:

    -
    -
    plot(st_geometry(db))
    +
    +
    -
    -
    -

    -

    Spatial distribution of AirBnb in San Diego

    +
    +

    Now this has the classic problem of cluttering: some portions of the map have so many points that we can’t tell what the distribution is like. To get around this issue, there are two solutions: binning and smoothing.

    -

    -
    -

    4.3 Binning

    +

    +4.3 Binning

    The two-dimensional sister of histograms are binning maps: we divide each of the two dimensions into “buckets”, and count how many points fall within each bucket. Unlike histograms, we encode that count with a color gradient rather than a bar chart over an additional dimension (for that, we would need a 3D plot). These “buckets” can be squares (left) or hexagons (right):

    -
          # Squared binning
    -# Set up plot
    -sqbin <- ggplot() + 
    -# Add 2D binning with the XY coordinates as
    -# a dataframe
    -  geom_bin2d(
    -    data=as.data.frame(st_coordinates(db)), 
    -    aes(x=X, y=Y)
    -  )
    -      # Hex binning
    -# Set up plot
    -hexbin <- ggplot() +
    -# Add hex binning with the XY coordinates as
    -# a dataframe 
    -  geom_hex(
    -    data=as.data.frame(st_coordinates(db)),
    -    aes(x=X, y=Y)
    -  ) +
    -# Use viridis for color encoding (recommended)
    -  scale_fill_continuous(type = "viridis")
    -      # Bind in subplots
    -grid.arrange(sqbin, hexbin, ncol=2)
    +
          # Squared binning
    +# Set up plot
    +sqbin <- ggplot( ) + 
    +# Add 2D binning with the XY coordinates as
    +# a dataframe
    +  geom_bin2d(
    +    data = as.data.frame( st_coordinates( db ) ), 
    +    aes( x = X, y = Y)
    +  ) + 
    +  # set theme 
    +  theme_plot_tufte()
    +      # Hex binning
    +# Set up plot
    +hexbin <- ggplot() +
    +# Add hex binning with the XY coordinates as
    +# a dataframe 
    +  geom_hex(
    +    data = as.data.frame( st_coordinates( db ) ),
    +    aes( x = X, y = Y)
    +  ) +
    +# Use viridis for color encoding (recommended)
    +  scale_fill_continuous( type = "viridis" ) +
    +  theme_plot_tufte()
    +      # Bind in subplots
    +grid.arrange( sqbin, hexbin, ncol = 2 ) 
    -

    +
    +

    +
    +
    -
    -
    -

    4.4 KDE

    +

    +4.4 KDE

    Kernel Density Estimation (KDE) is a technique that creates a continuous representation of the distribution of a given variable, such as house prices. Although theoretically it can be applied to any dimension, usually, KDE is applied to either one or two dimensions.

    -
    -

    4.4.1 One-dimensional KDE

    +

    +4.4.1 One-dimensional KDE

    KDE over a single dimension is essentially a contiguous version of a histogram. We can see that by overlaying a KDE on top of the histogram of logs that we have created before:

    -
    -
    # Create the base
    -base <- ggplot(db, aes(x=log_price))
    -# Histogram
    -hist <- base + 
    -  geom_histogram(bins=50, aes(y=..density..))
    -# Overlay density plot
    -kde <- hist + 
    -  geom_density(fill="#FF6666", alpha=0.5, colour="#FF6666")
    -kde
    +
    +
    # Create the base
    +base <- ggplot(db, aes(x=log_price))
    +# Histogram
    +hist <- base + 
    +  geom_histogram(bins=50, aes(y=..density..))
    +# Overlay density plot
    +kde <- hist + 
    +  geom_density(fill="#FF6666", alpha=0.5, colour="#FF6666") +
    +  theme_plot_tufte()
    +kde
    Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
     ℹ Please use `after_stat(density)` instead.
    -
    -
    -

    -

    Histogram and KDE of the log of AirBnb prices in San Diego

    +
    +

    The key idea is that we are smoothing out the discrete binning that the histogram involves. Note how the histogram is exactly the same as above shape-wise, but it has been rescalend on the Y axis to reflect probabilities rather than counts.

    -
    -
    -

    4.4.2 Two-dimensional (spatial) KDE

    +

    +4.4.2 Two-dimensional (spatial) KDE

    Geography, at the end of the day, is usually represented as a two-dimensional space where we locate objects using a system of dual coordinates, X and Y (or latitude and longitude). Thanks to that, we can use the same technique as above to obtain a smooth representation of the distribution of a two-dimensional variable. The crucial difference is that, instead of obtaining a curve as the output, we will create a surface, where intensity will be represented with a color gradient, rather than with the second dimension, as it is the case in the figure above.

    To create a spatial KDE in R, we can use general tooling for non-spatial points, such as the stat_density2d_filled method:

    -
    -
    # Create the KDE surface
    -kde <- ggplot(data = db) +
    -  stat_density2d_filled(alpha = 0.5,
    -    data = as.data.frame(st_coordinates(db)), 
    -    aes(x = X, y = Y),
    -    n = 16
    -  ) +
    -  # Tweak the color gradient
    -  scale_color_viridis_c() +
    -  # White theme
    -  theme_bw()
    -# Tip! Add invisible points to improve proportions
    -kde + geom_sf(alpha=0)
    +
    +
    # Create the KDE surface
    +kde <- ggplot(data = db) +
    +  stat_density2d_filled(alpha = 1,
    +    data = as.data.frame(st_coordinates(db)), 
    +    aes(x = X, y = Y),
    +    n = 100
    +  ) +
    +  # Tweak the color gradient
    +  scale_color_viridis_c() +
    +  # White theme
    +  theme_plot_tufte() 
    +kde
    -
    -
    -

    -

    KDE of AirBnb properties in San Diego

    +
    +

    -

    This approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library ggmap, which is designed to play nicely with the ggplot2 family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though.

    -
    -
    # Reproject coordinates
    -lon_lat <- st_transform(db, crs = 4326) %>%
    -  st_coordinates() %>%
    -  as.data.frame()
    -# Basemap
    -qmplot(
    -  X, 
    -  Y, 
    -  data = lon_lat, 
    -  geom="blank"
    -) +
    -  # KDE
    -  stat_density2d_filled(alpha = 0.5,
    -    data = lon_lat, 
    -    aes(x = X, y = Y),
    -    n = 16
    -  ) +
    -  # Tweak the color gradient
    -  scale_color_viridis_c()
    +

    This approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library basemapR, which is designed to play nicely with the ggplot2 family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though.

    +
    +
    bbox_db <- st_bbox(db)
    +ggplot() +
    +  base_map(bbox_db, increase_zoom = 2, basemap = "positron") +
    +  #geom_sf(data = db, fill = NA) +
    +  stat_density2d_filled(alpha = 0.7,
    +    data = as.data.frame(st_coordinates(db)), 
    +    aes(x = X, y = Y),
    +    n = 100
    +  )
    -
    -
    -

    -

    KDE of AirBnb properties in San Diego

    +
    +

    -
    -
    -
    -

    4.5 Spatial Interpolation

    +

    +4.5 Spatial Interpolation

    The previous section demonstrates how to visualize the distribution of a set of spatial objects represented as points. In particular, given a bunch of house locations, it shows how one can effectively visualize their distribution over space and get a sense of the density of occurrences. Such visualization, because it is based on KDE, is based on a smooth continuum, rather than on a discrete approach (as a choropleth would do, for example).

    Many times however, we are not particularly interested in learning about the density of occurrences, but about the distribution of a given value attached to each location. Think for example of weather stations and temperature: the location of the stations is no secret and rarely changes, so it is not of particular interest to visualize the density of stations; what we are usually interested instead is to know how temperature is distributed over space, given we only measure it in a few places. One could argue the example we have been working with so far, house prices in AirBnb, fits into this category as well: although where a house is advertised may be of relevance, more often we are interested in finding out what the “surface of price” looks like. Rather than where are most houses being advertised? we usually want to know where the most expensive or most affordable houses are located.

    In cases where we are interested in creating a surface of a given value, rather than a simple density surface of occurrences, KDE cannot help us. In these cases, what we are interested in is spatial interpolation, a family of techniques that aim at exactly that: creating continuous surfaces for a particular phenomenon (e.g. temperature, house prices) given only a finite sample of observations. Spatial interpolation is a large field of research that is still being actively developed and that can involve a substantial amount of mathematical complexity in order to obtain the most accurate estimates possible1. In this chapter, we will introduce the simplest possible way of interpolating values, hoping this will give you a general understanding of the methodology and, if you are interested, you can check out further literature. For example, Banerjee, Carlin, and Gelfand (2014) or Cressie (2015) are hard but good overviews.

    -
    -

    4.5.1 Inverse Distance Weight (IDW) interpolation

    +

    1 There is also an important economic incentive to do this: some of the most popular applications are in the oil and gas or mining industries. In fact, the very creator of this technique, Danie G. Krige, was a mining engineer. His name is usually used to nickname spatial interpolation as kriging.

    +4.5.1 Inverse Distance Weight (IDW) interpolation

    The technique we will cover here is called Inverse Distance Weighting, or IDW for convenience. Brunsdon and Comber (2015) offer a good description:

    In the inverse distance weighting (IDW) approach to interpolation, to estimate the value of \(z\) at location \(x\) a weighted mean of nearby observations is taken […]. To accommodate the idea that observations of \(z\) at points closer to \(x\) should be given more importance in the interpolation, greater weight is given to these points […]

    — Page 204

    The math2 is not particularly complicated and may be found in detail elsewhere (the reference above is a good starting point), so we will not spend too much time on it. More relevant in this context is the intuition behind. The idea is that we will create a surface of house price by smoothing many values arranged along a regular grid and obtained by interpolating from the known locations to the regular grid locations. This will give us full and equal coverage to soundly perform the smoothing.

    -

    Enough chat, let’s code3.

    +

    2 Essentially, for any point \(x\) in space, the IDW estimate for value \(z\) is equivalent to \(\hat{z} (x) = \dfrac{\sum_i w_i z_i}{\sum_i w_i}\) where \(i\) are the observations for which we do have a value, and \(w_i\) is a weight given to location \(i\) based on its distance to \(x\).

    3 If you want a complementary view of point interpolation in R, you can read more on this fantastic blog post

    Enough chat, let’s code3.

    From what we have just mentioned, there are a few steps to perform an IDW spatial interpolation:

    1. Create a regular grid over the area where we have house transactions.
    2. @@ -559,20 +541,20 @@

      4. First, let us set up a grid for the extent of the bounding box of our data (not the use of pipe, %>%, operator to chain functions):

      -
      -
      sd.grid <- db %>%
      -  st_bbox() %>%
      -  st_as_sfc() %>%
      -  st_make_grid(
      -    n = 100,
      -    what = "centers"
      -  ) %>%
      -  st_as_sf() %>%
      -  cbind(., st_coordinates(.))
      +

      4 For the relevant calculations, we will be using the gstat library.

      +
      sd.grid <- db %>%
      +  st_bbox() %>%
      +  st_as_sfc() %>%
      +  st_make_grid(
      +    n = 100,
      +    what = "centers"
      +  ) %>%
      +  st_as_sf() %>%
      +  cbind(., st_coordinates(.))

      The object sd.grid is a regular grid with 10,000 (\(100 \times 100\)) equally spaced cells:

      -
      sd.grid
      +
      sd.grid
      Simple feature collection with 10000 features and 2 fields
       Geometry type: POINT
      @@ -595,20 +577,20 @@ 

      -
      idw.hp <- idw(
      -  price ~ 1,         # Formula for IDW
      -  locations = db,    # Initial locations with values
      -  newdata=sd.grid,   # Locations we want predictions for
      -  nmax = 150         # Limit the number of neighbours for IDW
      -)
      +
      idw.hp <- idw(
      +  price ~ 1,         # Formula for IDW
      +  locations = db,    # Initial locations with values
      +  newdata=sd.grid,   # Locations we want predictions for
      +  nmax = 150         # Limit the number of neighbours for IDW
      +)
      [inverse distance weighted interpolation]

      Boom! We’ve got it. Let us pause for a second to see how we just did it. First, we pass price ~ 1. This specifies the formula we are using to model house prices. The name on the left of ~ represents the variable we want to explain, while everything to its right captures the explanatory variables. Since we are considering the simplest possible case, we do not have further variables to add, so we simply write 1. Then we specify the original locations for which we do have house prices (our original db object), and the points where we want to interpolate the house prices (the sd.grid object we just created above). One more note: by default, idw uses all the available observations, weighted by distance, to provide an estimate for a given point. If you want to modify that and restrict the maximum number of neighbors to consider, you need to tweak the argument nmax, as we do above by using the 150 nearest observations to each point5.

      -

      The object we get from idw is another spatial table, just as db, containing the interpolated values. As such, we can inspect it just as with any other of its kind. For example, to check out the top of the estimated table:

      +

      5 Have a play with this because the results do change significantly. Can you reason why?

      The object we get from idw is another spatial table, just as db, containing the interpolated values. As such, we can inspect it just as with any other of its kind. For example, to check out the top of the estimated table:

      -
      head(idw.hp)
      +
      head(idw.hp)
      Simple feature collection with 6 features and 2 fields
       Geometry type: POINT
      @@ -625,50 +607,55 @@ 

      -

      4.5.2 A surface of housing prices

      +

    +4.5.2 A surface of housing prices

    Once we have the IDW object computed, we can plot it to explore the distribution, not of AirBnb locations in this case, but of house prices over the geography of San Diego. To do this using ggplot2, we first append the coordinates of each grid cell as columns of the table:

    -
    idw.hp = idw.hp %>%
    -  cbind(st_coordinates(.))
    +
    idw.hp = idw.hp %>%
    +  cbind(st_coordinates(.))

    Now, we can visualise the surface using standard ggplot2 tools:

    -
    ggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +
    -  geom_raster()
    +
    ggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +
    +  geom_raster()
    -

    +
    +

    +
    +

    And we can “dress it up” a bit further:

    -
    ggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +
    -  geom_raster() +
    -  scale_fill_viridis_b() +
    -  theme_void() +
    -  geom_sf(alpha=0)
    +
    ggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +
    +  geom_raster() +
    +  scale_fill_viridis_b() +
    +  theme_void() +
    +  geom_sf(alpha=0)
    -

    +
    +

    +
    +

    Looking at this, we can start to tell some patterns. To bring in context, it would be great to be able to add a basemap layer, as we did for the KDE. This is conceptually very similar to what we did above, starting by reprojecting the points and continuing by overlaying them on top of the basemap. However, technically speaking it is not possible because ggmap –the library we have been using to display tiles from cloud providers– does not play well with our own rasters (i.e. the price surface). At the moment, it is surprisingly tricky to get this to work, so we will park it for now6.

    -
    -
    -

    4.5.3 “What should the next house’s price be?”

    +

    6 BONUS if you can figure out a way to do it yourself!

    +4.5.3 “What should the next house’s price be?” +

    The last bit we will explore in this session relates to prediction for new values. Imagine you are a real state data scientist working for Airbnb and your boss asks you to give an estimate of how much a new house going into the market should cost. The only information you have to make such a guess is the location of the house. In this case, the IDW model we have just fitted can help you. The trick is realizing that, instead of creating an entire grid, all we need is to obtain an estimate of a single location.

    Let us say, a new house is going to be advertised on the coordinates X = -117.02259063720702, Y = 32.76511965117273 as expressed in longitude and latitude. In that case, we can do as follows:

    -
    pt <- c(X = -117.02259063720702, Y = 32.76511965117273) %>%
    -  st_point() %>%
    -  st_sfc() %>%
    -  st_sf(crs = "EPSG:4326") %>%
    -  st_transform(st_crs(db))
    -idw.one <- idw(price ~ 1, locations=db, newdata=pt)
    +
    pt <- c(X = -117.02259063720702, Y = 32.76511965117273) %>%
    +  st_point() %>%
    +  st_sfc() %>%
    +  st_sf(crs = "EPSG:4326") %>%
    +  st_transform(st_crs(db))
    +idw.one <- idw(price ~ 1, locations=db, newdata=pt)
    [inverse distance weighted interpolation]
    -
    idw.one
    +
    idw.one
    Simple feature collection with 1 feature and 2 fields
     Geometry type: POINT
    @@ -680,16 +667,14 @@ 

    7.

    -

    -
    -
    -

    4.6 Questions

    +

    7 PRO QUESTION Is that house expensive or cheap, as compared to the other houses sold in this dataset? Can you figure out where the house is in the distribution?

    +4.6 Questions

    We will be using the Madrid AirBnb dataset:

    -
    mad_abb <- st_read("data/assignment_1_madrid/madrid_abb.gpkg")
    +
    mad_abb <- st_read("data/assignment_1_madrid/madrid_abb.gpkg")
    Reading layer `madrid_abb' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' 
       using driver `GPKG'
     Simple feature collection with 18399 features and 16 fields
     Geometry type: POINT
    @@ -700,7 +685,7 @@ 

    -
    colnames(mad_abb)
    +
    colnames(mad_abb)
     [1] "price"           "price_usd"       "log1p_price_usd" "accommodates"   
      [5] "bathrooms_text"  "bathrooms"       "bedrooms"        "beds"           
    @@ -717,39 +702,26 @@ 

    -
    -
    -
      -
    1. There is also an important economic incentive to do this: some of the most popular applications are in the oil and gas or mining industries. In fact, the very creator of this technique, Danie G. Krige, was a mining engineer. His name is usually used to nickname spatial interpolation as kriging.↩︎

    2. -
    3. Essentially, for any point \(x\) in space, the IDW estimate for value \(z\) is equivalent to \(\hat{z} (x) = \dfrac{\sum_i w_i z_i}{\sum_i w_i}\) where \(i\) are the observations for which we do have a value, and \(w_i\) is a weight given to location \(i\) based on its distance to \(x\).↩︎

    4. -
    5. If you want a complementary view of point interpolation in R, you can read more on this fantastic blog post↩︎

    6. -
    7. For the relevant calculations, we will be using the gstat library.↩︎

    8. -
    9. Have a play with this because the results do change significantly. Can you reason why?↩︎

    10. -
    11. BONUS if you can figure out a way to do it yourself!↩︎

    12. -
    13. PRO QUESTION Is that house expensive or cheap, as compared to the other houses sold in this dataset? Can you figure out where the house is in the distribution?↩︎

    14. -
    -
    - -
    - -
    + \ No newline at end of file diff --git a/docs/04-points_files/figure-html/unnamed-chunk-10-1.png b/docs/04-points_files/figure-html/unnamed-chunk-10-1.png index c029548..af2a820 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-10-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-10-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-11-1.png b/docs/04-points_files/figure-html/unnamed-chunk-11-1.png new file mode 100644 index 0000000..e860e7a Binary files /dev/null and b/docs/04-points_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-16-1.png b/docs/04-points_files/figure-html/unnamed-chunk-16-1.png deleted file mode 100644 index e494361..0000000 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-16-1.png and /dev/null differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-17-1.png b/docs/04-points_files/figure-html/unnamed-chunk-17-1.png index 1ee0fc3..bf526d5 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-17-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-18-1.png b/docs/04-points_files/figure-html/unnamed-chunk-18-1.png new file mode 100644 index 0000000..038b75f Binary files /dev/null and b/docs/04-points_files/figure-html/unnamed-chunk-18-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-4-1.png b/docs/04-points_files/figure-html/unnamed-chunk-4-1.png deleted file mode 100644 index 4af83aa..0000000 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-4-1.png and /dev/null differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-5-1.png b/docs/04-points_files/figure-html/unnamed-chunk-5-1.png index 62e887e..9369a79 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-5-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-6-1.png b/docs/04-points_files/figure-html/unnamed-chunk-6-1.png index c99e6ea..eb9b038 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-6-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-6-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-7-1.png b/docs/04-points_files/figure-html/unnamed-chunk-7-1.png index a9a732d..c99e6ea 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-7-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-8-1.png b/docs/04-points_files/figure-html/unnamed-chunk-8-1.png index 65fa878..69c0c1e 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-8-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/docs/04-points_files/figure-html/unnamed-chunk-9-1.png b/docs/04-points_files/figure-html/unnamed-chunk-9-1.png index b25fe1d..21fc612 100644 Binary files a/docs/04-points_files/figure-html/unnamed-chunk-9-1.png and b/docs/04-points_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/docs/05-flows.html b/docs/05-flows.html index d5e562c..20f3f86 100644 --- a/docs/05-flows.html +++ b/docs/05-flows.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 5  Spatial Interaction Modelling - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    5  Spatial Interaction Modelling

    +
    +

    5  Spatial Interaction Modelling

    @@ -296,49 +301,46 @@

    offer a historical overview of spatial interaction models and illustration of use cases. -
  • Rowe, Lovelace, and Dennett (2022) provide a good overview of the existing limitations and opportunities of spatial interaction modelling.
  • -
  • Singleton (2017), an online short course on R for Geographic Data Science and Urban Analytics. In particular, the section on mapping flows is specially relevant here.
  • +
  • +Fotheringham and O’Kelly (1989) offer a historical overview of spatial interaction models and illustration of use cases.
  • +
  • +Rowe, Lovelace, and Dennett (2022) provide a good overview of the existing limitations and opportunities of spatial interaction modelling.
  • +
  • +Singleton (2017), an online short course on R for Geographic Data Science and Urban Analytics. In particular, the section on mapping flows is specially relevant here.
  • The predictive checks section draws heavily from Gelman and Hill (2006), in particular Chapters 6 and 7.
  • -
    -

    5.1 Dependencies

    -

    We will rely on the following libraries in this section, all of them included in Section 1.4.1:

    +

    +5.1 Dependencies

    +

    We will rely on the following libraries in this section, all of them included in Section 1.4.1:

    -
    # Data management
    -library(tidyverse)
    -# Spatial Data management
    -library(sf)
    -library(sp)
    -# Pretty graphics
    -library(ggplot2)
    -# Thematic maps
    -library(tmap)
    -# Pretty maps
    -library(ggmap)
    -# Simulation methods
    -library(arm)
    +
    # Data management
    +library(tidyverse)
    +# Spatial Data management
    +library(sf)
    +library(sp)
    +# Pretty graphics
    +library(ggplot2)
    +# Thematic maps
    +library(tmap)
    +# Add basemaps
    +library(basemapR)
    +# Simulation methods
    +library(arm)

    In this chapter we will show a slightly different way of managing spatial data in R. Although most of the functionality will be similar to that seen in previous chapters, we will not rely on the “sf stack” and we will instead show how to read and manipulate data using the more traditional sp stack. Although this approach is being slowly phased out, it is still important to be aware of its existence and its differences with more modern approaches.

    -

    Before we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the sf_bikes folder with the data lives.

    -
    -
    setwd('.')
    -
    -
    -
    -

    5.2 Data

    +

    +5.2 Data

    In this note, we will use data from the city of San Francisco representing bike trips on their public bike share system. The original source is the SF Open Data portal (link) and the dataset comprises both the location of each station in the Bay Area as well as information on trips (station of origin to station of destination) undertaken in the system from September 2014 to August 2015 and the following year. Since this note is about modeling and not data preparation, a cleanly reshaped version of the data, together with some additional information, has been created and placed in the sf_bikes folder. The data file is named flows.geojson and, in case you are interested, the (Python) code required to created from the original files in the SF Data Portal is also available on the flows_prep.ipynb notebook [url], also in the same folder.

    Let us then directly load the file with all the information necessary:

    -
    db <- st_read('./data/sf_bikes/flows.geojson')
    +
    db <- st_read('./data/sf_bikes/flows.geojson')
    Reading layer `flows' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/sf_bikes/flows.geojson' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/sf_bikes/flows.geojson' 
       using driver `GeoJSON'
     Simple feature collection with 1722 features and 9 fields
     Geometry type: LINESTRING
    @@ -346,13 +348,11 @@ 

    #rownames(db@data) <- db$flow_id
    -#db@data$flow_id <- NULL

    Note how the interface is slightly different since we are reading a GeoJSON file instead of a shapefile.

    The data contains the geometries of the flows, as calculated from the Google Maps API, as well as a series of columns with characteristics of each flow:

    -
    head(db)
    +
    head(db)
    Simple feature collection with 6 features and 9 fields
     Geometry type: LINESTRING
    @@ -376,165 +376,183 @@ 

    -

    5.3Seeing” flows

    +

    +5.3Seeing” flows

    The easiest way to get a quick preview of what the data looks like spatially is to make a simple plot:

    -
    -
    plot(db$geometry)
    +
    +
    plot(db$geometry)
    -
    -
    -

    -

    Potential routes

    +
    +

    Equally, if we want to visualize a single route, we can simply subset the table. For example, to get the shape of the trip from station 39 to station 48, we can:

    -
    -
    db %>% 
    -  dplyr::filter(orig == 39 & dest == 48) %>% 
    -  ggplot(data = .) + 
    -  geom_sf(color = "black", 
    -          size = 0.1) +
    -  theme_void()
    +
    +
    db %>% 
    +  dplyr::filter(orig == 39 & dest == 48) %>% 
    +  ggplot(data = .) + 
    +  geom_sf(color = "black", 
    +          size = 0.1) +
    +  theme_void()
    -
    -
    -

    -

    Trip from station 39 to 48

    +
    +

    or, for the most popular route, we can:

    -
    -
    most_pop <- db %>% 
    -  dplyr::filter(trips15 == max(trips15))
    +
    +
    most_pop <- db %>% 
    +  dplyr::filter(trips15 == max(trips15))

    These however do not reveal a lot: there is no geographical context (why are there so many routes along the NE?) and no sense of how volumes of bikers are allocated along different routes. Let us fix those two.

    -

    The easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using ggmap:

    +

    The easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using basemapR:

    -
    bbox_db <- st_bbox(db)
    -names(bbox_db) <- c("left", "bottom", "right", "top")
    -
    -SanFran <- get_stamenmap(
    -  bbox_db, 
    -  zoom = 14, 
    -  maptype = "toner-lite"
    -  )
    -
    -

    and make sure it looks like we intend it to look:

    -
    -
    ggmap(SanFran)
    +
    # create a bounding box
    +bbox_db <- st_bbox(db)
    +# download a basemap using ggplot and basemapR
    +ggplot() +
    +  base_map(bbox_db, increase_zoom = 2, basemap = "positron") +
    +  geom_sf(data = db, fill = NA, colour = "transparent") 
    -

    +
    +

    +
    +

    Now to combine tiles and routes, we need to pull out the coordinates that make up each line. For the route example above, this would be:

    -
    -
    xys1 <- as.data.frame(st_coordinates(most_pop))
    -
    -

    Now we can plot the route1 (note we also dim down the background to focus the attention on flows):

    -
    -
    ggmap(SanFran, darken=0.5) + 
    -  geom_path(
    -    aes(x=X, y=Y), 
    -    data=xys1,
    -    size=1,
    -    color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784),
    -    lineend='round'
    -    )
    -
    -
    Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
    -ℹ Please use `linewidth` instead.
    +
    +
    xys1 <- as.data.frame(st_coordinates(most_pop))
    +

    Now we can plot the route (note we also dim down the background to focus the attention on flows):

    + +
    +
    +
    +
    + +
    +
    +

    Task

    +

    Can you plot the route for the largest climb?

    +
    +
    +
    +
    +
    ggplot() +
    +  base_map(bbox_db, increase_zoom = 2, basemap = "dark") +
    +    geom_sf( data = db, fill = NA, colour = "transparent") +
    +  geom_path( data = xys1, 
    +             aes( x = X, y = Y ), 
    +             #size = 1,
    +             color = "green",
    +             lineend ='round'
    +             )
    -

    +
    +

    +
    +

    Now we can plot all of the lines by using a short for loop to build up the table:

    -
    # Set up shell data.frame
    -lines <- data.frame(
    -  lat = numeric(0), 
    -  lon = numeric(0), 
    -  trips = numeric(0),
    -  id = numeric(0)
    -)
    -# Run loop
    -for(x in 1:nrow(db)){
    -  # Pull out row
    -  r <- db[x, ]
    -  # Extract lon/lat coords
    -  xys <- as.data.frame(st_coordinates(r))
    -  names(xys) <- c('lon', 'lat')
    -  # Insert trips and id
    -  xys['trips'] <- r$trips15
    -  xys['id'] <- x
    -  # Append them to `lines`
    -  lines <- rbind(lines, xys)
    -}
    +
    # Set up shell data.frame
    +lines <- data.frame(
    +  lat = numeric(0), 
    +  lon = numeric(0), 
    +  trips = numeric(0),
    +  id = numeric(0)
    +)
    +# Run loop
    +for(x in 1:nrow(db)){
    +  # Pull out row
    +  r <- db[x, ]
    +  # Extract lon/lat coords
    +  xys <- as.data.frame(st_coordinates(r))
    +  names(xys) <- c('lon', 'lat')
    +  # Insert trips and id
    +  xys['trips'] <- r$trips15
    +  xys['id'] <- x
    +  # Append them to `lines`
    +  lines <- rbind(lines, xys)
    +}

    Now we can go on and plot all of them:

    -
    -
    ggmap(SanFran, darken=0.75) + 
    -  geom_path(
    -    aes(x=lon, y=lat, group=id),
    -    data=lines,
    -    size=0.1,
    -    color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784),
    -    lineend='round'
    -  )
    +
    +
    ggplot() +
    +  # call basemap
    +  base_map(bbox_db, increase_zoom = 2, basemap = "dark") +
    +  geom_sf(data = db, fill = NA, colour = "transparent") +
    +  # add data
    +  geom_path( data = lines, 
    +             aes(x=lon, y=lat, 
    +                 group=id
    +                 ), 
    +             size = 1,
    +             color = "green",
    +             lineend = 'round')
    -

    +
    +

    +
    +

    Finally, we can get a sense of the distribution of the flows by associating a color gradient to each flow based on its number of trips:

    -
    -
    ggmap(SanFran, darken=0.75) + 
    -  geom_path(
    -    aes(x=lon, y=lat, group=id, colour=trips),
    -    data=lines,
    -    size=log1p(lines$trips / max(lines$trips)),
    -    lineend='round'
    -  ) +
    -  scale_colour_gradient(
    -    low='grey', high='#07eda0'
    -  ) +
    -  theme(
    -    axis.text.x = element_blank(),
    -    axis.text.y = element_blank(),
    -    axis.ticks = element_blank()
    -  )
    +
    +
    ggplot() +
    +  # call basemap
    +  base_map(bbox_db, increase_zoom = 2, basemap = "dark") +
    +  geom_sf(data = db, fill = NA, colour = "transparent") +
    +  # add flow data
    +  geom_path( data = lines, 
    +             aes(x = lon, y = lat, group = id, colour = trips ), 
    +             size=log1p(lines$trips / max(lines$trips)),
    +             lineend = 'round'
    +    ) +
    +  # create a colour palette
    +  scale_colour_gradient(
    +    low='#440154FF', high='#FDE725FF'
    +  ) +
    +  theme(
    +    axis.text.x = element_blank(),
    +    axis.text.y = element_blank(),
    +    axis.ticks = element_blank()
    +  )
    -

    +
    +

    +
    +

    Note how we transform the size so it’s a proportion of the largest trip and then it is compressed with a logarithm.

    -
    -
    -

    5.4 Modelling flows

    -

    Now we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from Gelman and Hill (2006) such as predictive performance checks2 (PPC)

    -

    Before we start running regressions, let us first standardize the predictors so we can interpret the intercept as the average flow when all the predictors take the average value, and so we can interpret the model coefficients as changes in standard deviation units:

    +

    +5.4 Modelling flows

    +

    Now we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from Gelman and Hill (2006) such as predictive performance checks1 (PPC)

    +

    1 For a more elaborate introduction to PPC, have a look at Chapters 7 and 8.

    Before we start running regressions, let us first standardize the predictors so we can interpret the intercept as the average flow when all the predictors take the average value, and so we can interpret the model coefficients as changes in standard deviation units:

    -
    # Scale all the table
    -db_std <- db %>% mutate(across(where(is.numeric), scale))
    -
    -# Reset trips as we want the original version
    -db_std$trips15 <- db$trips15
    -db_std$trips16 <- db$trips16
    -
    -# Reset origin and destination station and express them as factors
    -db_std$orig <- as.factor(db$orig)
    -db_std$dest <- as.factor(db$dest)
    +
    # Scale all the table
    +db_std <- db %>% mutate(across(where(is.numeric), scale))
    +
    +# Reset trips as we want the original version
    +db_std$trips15 <- db$trips15
    +db_std$trips16 <- db$trips16
    +
    +# Reset origin and destination station and express them as factors
    +db_std$orig <- as.factor(db$orig)
    +db_std$dest <- as.factor(db$dest)

    Baseline model

    One of the simplest possible models we can fit in this context is a linear model that explains the number of trips as a function of the straight distance between the two stations and total amount of climb and downhill. We will take this as the baseline on which we can further build later:

    -
    m1 <- lm('trips15 ~ straight_dist + total_up + total_down', data=db_std)
    -summary(m1)
    +
    m1 <- lm('trips15 ~ straight_dist + total_up + total_down', data=db_std)
    +summary(m1)
    
     Call:
    @@ -560,26 +578,29 @@ 

    To explore how good this model is, we will be comparing the predictions the model makes about the number of trips each flow should have with the actual number of trips. A first approach is to simply plot the distribution of both variables:

    -
    -
    plot(
    -  density(m1$fitted.values), 
    -  xlim=c(-100, max(db_std$trips15)),
    -  main=''
    -)
    -lines(
    -  density(db_std$trips15), 
    -  col='red',
    -  main=''
    -)
    -legend(
    -  'topright', 
    -  c('Predicted', 'Actual'),
    -  col=c('black', 'red'),
    -  lwd=1
    -)
    -title(main="Predictive check, point estimates - Baseline model")
    +
    +
    plot(
    +  density( m1$fitted.values ), 
    +  xlim = c(-100, max( db_std$trips15 )),
    +  main=''
    +)
    +lines(
    +  density( db_std$trips15 ), 
    +  col='red',
    +  main=''
    +)
    +legend(
    +  'topright', 
    +  c('Predicted', 'Actual'),
    +  col=c('black', 'red'),
    +  lwd=1
    +)
    +title(main="Predictive check, point estimates - Baseline model")
    -

    +
    +

    +
    +

    The plot makes pretty obvious that our initial model captures very few aspects of the distribution we want to explain. However, we should not get too attached to this plot just yet. What it is showing is the distribution of predicted point estimates from our model. Since our model is not deterministic but inferential, there is a certain degree of uncertainty attached to its predictions, and that is completely absent from this plot.

    @@ -590,104 +611,110 @@

    Taking these two sources into consideration means that the black line in the plot above represents only the behaviour of our model we expect if the error term is absent (no predictive uncertainty) and the coefficients are the true estimates (no inferential uncertainty). However, this is not necessarily the case as our estimate for the uncertainty of the error term is certainly not zero, and our estimates for each parameter are also subject to a great deal of inferential variability. We do not know to what extent other outcomes would be just as likely. Predictive checking relates to simulating several feasible scenarios under our model and use those to assess uncertainty and to get a better grasp of the quality of our predictions.

    Technically speaking, to do this, we need to build a mechanism to obtain a possible draw from our model and then repeat it several times. The first part of those two steps can be elegantly dealt with by writing a short function that takes a given model and a set of predictors, and produces a possible random draw from such model:

    -
    generate_draw <- function(m){
    -  # Set up predictors matrix
    -  x <- model.matrix(m)
    -  # Obtain draws of parameters (inferential uncertainty)
    -  sim_bs <- sim(m, 1)
    -  # Predicted value
    -  mu <- x %*% sim_bs@coef[1, ]
    -  # Draw
    -  n <- length(mu)
    -  y_hat <- rnorm(n, mu, sim_bs@sigma[1])
    -  return(y_hat)
    -}
    +
    generate_draw <- function(m){
    +  # Set up predictors matrix
    +  x <- model.matrix( m )
    +  # Obtain draws of parameters (inferential uncertainty)
    +  sim_bs <- sim( m, 1)
    +  # Predicted value
    +  mu <- x %*% sim_bs@coef[1, ]
    +  # Draw
    +  n <- length( mu )
    +  y_hat <- rnorm( n, mu, sim_bs@sigma[1])
    +  return(y_hat)
    +}

    This function takes a model m and the set of covariates x used and returns a random realization of predictions from the model. To get a sense of how this works, we can get and plot a realization of the model, compared to the expected one and the actual values:

    -
    -
    new_y <- generate_draw(m1)
    -
    -plot(
    -  density(m1$fitted.values), 
    -  xlim=c(-100, max(db_std$trips15)),
    -  ylim=c(0, max(c(
    -                   max(density(m1$fitted.values)$y), 
    -                   max(density(db_std$trips15)$y)
    -                   )
    -                )
    -         ),
    -  col='black',
    -  main=''
    -)
    -lines(
    -  density(db_std$trips15), 
    -  col='red',
    -  main=''
    -)
    -lines(
    -  density(new_y), 
    -  col='green',
    -  main=''
    -)
    -legend(
    -  'topright', 
    -  c('Predicted', 'Actual', 'Simulated'),
    -  col=c('black', 'red', 'green'),
    -  lwd=1
    -)
    +
    +
    new_y <- generate_draw(m1)
    +
    +plot(
    +  density( m1$fitted.values ), 
    +  xlim = c(-100, max( db_std$trips15 )),
    +  ylim = c(0, max(c(
    +                   max( density( m1$fitted.values)$y ), 
    +                   max( density( db_std$trips15)$y )
    +                   )
    +                )
    +         ),
    +  col = 'black',
    +  main = ''
    +)
    +lines(
    +  density( db_std$trips15 ), 
    +  col = 'red',
    +  main = ''
    +)
    +lines(
    +  density( new_y ), 
    +  col = 'green',
    +  main = ''
    +)
    +legend(
    +  'topright', 
    +  c('Predicted', 'Actual', 'Simulated'),
    +  col = c( 'black', 'red', 'green' ),
    +  lwd = 1
    +)
    -

    +
    +

    +
    +

    Once we have this “draw engine”, we can set it to work as many times as we want using a simple for loop. In fact, we can directly plot these lines as compared to the expected one and the trip count:

    -
    -
    plot(
    -  density(m1$fitted.values), 
    -  xlim=c(-100, max(db_std$trips15)),
    -  ylim=c(0, max(c(
    -               max(density(m1$fitted.values)$y), 
    -               max(density(db_std$trips15)$y)
    -               )
    -            )
    -     ),
    -  col='white',
    -  main=''
    -)
    -# Loop for realizations
    -for(i in 1:250){
    -  tmp_y <- generate_draw(m1)
    -  lines(density(tmp_y),
    -        col='grey',
    -        lwd=0.1
    -        )
    -}
    -#
    -lines(
    -  density(m1$fitted.values), 
    -  col='black',
    -  main=''
    -)
    -lines(
    -  density(db_std$trips15), 
    -  col='red',
    -  main=''
    -)
    -legend(
    -  'topright', 
    -  c('Actual', 'Predicted', 'Simulated (n=250)'),
    -  col=c('red', 'black', 'grey'),
    -  lwd=1
    -)
    -title(main="Predictive check - Baseline model")
    +
    +
    plot(
    +  density( m1$fitted.values ), 
    +  xlim = c(-100, max( db_std$trips15 )),
    +  ylim = c(0, max(c(
    +               max( density( m1$fitted.values)$y ), 
    +               max( density( db_std$trips15)$y )
    +               )
    +            )
    +     ),
    +  col='white',
    +  main=''
    +)
    +# Loop for realizations
    +for(i in 1:250){
    +  tmp_y <- generate_draw( m1 )
    +  lines( density( tmp_y ),
    +        col = 'grey',
    +        lwd = 0.1
    +        )
    +}
    +#
    +lines(
    +  density( m1$fitted.values ), 
    +  col = 'black',
    +  main = ''
    +)
    +lines(
    +  density( db_std$trips15 ), 
    +  col = 'red',
    +  main = ''
    +)
    +legend(
    +  'topright', 
    +  c('Actual', 'Predicted', 'Simulated (n=250)'),
    +  col = c('red', 'black', 'grey'),
    +  lwd = 1
    +)
    +title(main="Predictive check - Baseline model")
    -

    +
    +

    +
    -

    The plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our “inferential engine”, which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model3 is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution.

    -

    It is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate4, our capabilities to infer about the more general underlying process are fairly limited.

    +
    +

    The plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our “inferential engine”, which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model2 is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution.

    +

    2 The \(R^2\) of our model is around 2%

    3 which they are not really, in light of the comparison between the black and red lines.

    It is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate3, our capabilities to infer about the more general underlying process are fairly limited.

    Improving the model

    -

    The bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular5. These three routes are aligned around the following principles:

    -
      +

      The bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular4. These three routes are aligned around the following principles:

      +

      4 These principles are general and can be applied to pretty much any modeling exercise you run into. The specific approaches we take in this note relate to spatial interaction models

      1. Use better approximations to model your dependent variable.
      2. Recognize the structure of your data.
      3. Get better predictors.
      4. @@ -695,109 +722,115 @@

      5. Use better approximations to model your dependent variable
      6. -

        Standard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for6; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal.

        -

        One of the most common distributions for this cases is the Poisson, which can be incorporated through a general linear model (or GLM). The underlying assumption here is that instead of \(T_{ij} \sim N(\mu_{ij}, \sigma)\), our model now follows:

        +

        Standard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for5; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal.

        +

        5 For an illustration of this, consider the amount of probability mass to the left of zero in the predictive checks above.

        One of the most common distributions for this cases is the Poisson, which can be incorporated through a general linear model (or GLM). The underlying assumption here is that instead of \(T_{ij} \sim N(\mu_{ij}, \sigma)\), our model now follows:

        \[ T_{ij} \sim Poisson (\exp^{X_{ij}\beta}) \]

        As usual, such a model is easy to run in R:

        -
        m2 <- glm(
        -  'trips15 ~ straight_dist + total_up + total_down', 
        -  data=db_std,
        -  family=poisson,
        -)
        +
        m2 <- glm(
        +  'trips15 ~ straight_dist + total_up + total_down', 
        +  data=db_std,
        +  family=poisson,
        +)

        Now let’s see how much better, if any, this approach is. To get a quick overview, we can simply plot the point predictions:

        -
        -
        plot(
        -  density(m2$fitted.values), 
        -  xlim=c(-100, max(db_std$trips15)),
        -  ylim=c(0, max(c(
        -               max(density(m2$fitted.values)$y), 
        -               max(density(db_std$trips15)$y)
        -               )
        -            )
        -   ),
        -  col='black',
        -  main=''
        -)
        -lines(
        -  density(db_std$trips15), 
        -  col='red',
        -  main=''
        -)
        -legend(
        -  'topright', 
        -  c('Predicted', 'Actual'),
        -  col=c('black', 'red'),
        -  lwd=1
        -)
        -title(main="Predictive check, point estimates - Poisson model")
        +
        +
        plot(
        +  density( m2$fitted.values ), 
        +  xlim = c( -100, max( db_std$trips15 )),
        +  ylim = c( 0, max(c(
        +               max( density( m2$fitted.values)$y ), 
        +               max( density(db_std$trips15)$y )
        +               )
        +            )
        +   ),
        +  col = 'black',
        +  main = ''
        +)
        +lines(
        +  density( db_std$trips15 ), 
        +  col = 'red',
        +  main = ''
        +)
        +legend(
        +  'topright', 
        +  c('Predicted', 'Actual'),
        +  col = c('black', 'red'),
        +  lwd = 1
        +)
        +title(main = "Predictive check, point estimates - Poisson model")
        -

        +
        +

        +
        +

        To incorporate uncertainty to these predictions, we need to tweak our generate_draw function so it accommodates the fact that our model is not linear anymore.

        -
        generate_draw_poi <- function(m){
        -  # Set up predictors matrix
        -  x <- model.matrix(m)
        -  # Obtain draws of parameters (inferential uncertainty)
        -  sim_bs <- sim(m, 1)
        -  # Predicted value
        -  xb <- x %*% sim_bs@coef[1, ]
        -  #xb <- x %*% m$coefficients
        -  # Transform using the link function
        -  mu <- exp(xb)
        -  # Obtain a random realization
        -  y_hat <- rpois(n=length(mu), lambda=mu)
        -  return(y_hat)
        -}
        +
        generate_draw_poi <- function(m){
        +  # Set up predictors matrix
        +  x <- model.matrix( m )
        +  # Obtain draws of parameters (inferential uncertainty)
        +  sim_bs <- sim( m, 1 )
        +  # Predicted value
        +  xb <- x %*% sim_bs@coef[1, ]
        +  #xb <- x %*% m$coefficients
        +  # Transform using the link function
        +  mu <- exp( xb )
        +  # Obtain a random realization
        +  y_hat <- rpois( n = length( mu ), lambda = mu)
        +  return(y_hat)
        +}

        And then we can examine both point predictions an uncertainty around them:

        -
        plot(
        -  density(m2$fitted.values), 
        -  xlim=c(-100, max(db_std$trips15)),
        -  ylim=c(0, max(c(
        -               max(density(m2$fitted.values)$y), 
        -               max(density(db_std$trips15)$y)
        -               )
        -            )
        -   ),
        -  col='white',
        -  main=''
        -)
        -# Loop for realizations
        -for(i in 1:250){
        -  tmp_y <- generate_draw_poi(m2)
        -  lines(
        -    density(tmp_y),
        -    col='grey',
        -    lwd=0.1
        -  )
        -}
        -#
        -lines(
        -  density(m2$fitted.values), 
        -  col='black',
        -  main=''
        -)
        -lines(
        -  density(db_std$trips15), 
        -  col='red',
        -  main=''
        -)
        -legend(
        -  'topright', 
        -  c('Predicted', 'Actual', 'Simulated (n=250)'),
        -  col=c('black', 'red', 'grey'),
        -  lwd=1
        -)
        -title(main="Predictive check - Poisson model")
        +
        plot(
        +  density( m2$fitted.values ), 
        +  xlim = c(-100, max( db_std$trips15 )),
        +  ylim = c(0, max(c(
        +               max( density( m2$fitted.values)$y ), 
        +               max( density( db_std$trips15)$y )
        +               )
        +            )
        +   ),
        +  col = 'white',
        +  main = ''
        +)
        +# Loop for realizations
        +for(i in 1:250){
        +  tmp_y <- generate_draw_poi( m2 )
        +  lines(
        +    density( tmp_y ),
        +    col = 'grey',
        +    lwd = 0.1
        +  )
        +}
        +#
        +lines(
        +  density( m2$fitted.values ), 
        +  col = 'black',
        +  main = ''
        +)
        +lines(
        +  density( db_std$trips15 ), 
        +  col = 'red',
        +  main = ''
        +)
        +legend(
        +  'topright', 
        +  c('Predicted', 'Actual', 'Simulated (n=250)'),
        +  col = c('black', 'red', 'grey'),
        +  lwd = 1
        +)
        +title( main = "Predictive check - Poisson model")
        -

        +
        +

        +
        +

        Voila! Although the curve is still a bit off, centered too much to the right of the actual data, our predictive simulation leaves the fitted values right in the middle. This speaks to a better fit of the model to the actual distribution othe original data follow.

        @@ -809,42 +842,45 @@

        \[ T_{ij} = X_{ij}\beta + \delta_i + \delta_j + \epsilon_{ij} \]

        -

        where \(\delta_i\) and \(\delta_j\) are origin and destination station fixed effects7, and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce orig and dest in the the model:

        -
        -
        m3 <- glm(
        -  'trips15 ~ straight_dist + total_up + total_down + orig + dest', 
        -  data=db_std,
        -  family=poisson
        -)
        -
        -

        And with our new model, we can have a look at how well it does at predicting the overall number of trips8:

        -
        -
        plot(
        -  density(m3$fitted.values), 
        -  xlim=c(-100, max(db_std$trips15)),
        -  ylim=c(0, max(c(
        -               max(density(m3$fitted.values)$y), 
        -               max(density(db_std$trips15)$y)
        -               )
        -            )
        -   ),
        -  col='black',
        -  main=''
        -)
        -lines(
        -  density(db_std$trips15), 
        -  col='red',
        -  main=''
        -)
        -legend(
        -  'topright', 
        -  c('Predicted', 'Actual'),
        -  col=c('black', 'red'),
        -  lwd=1
        -)
        -title(main="Predictive check - Orig/dest FE Poisson model")
        +

        where \(\delta_i\) and \(\delta_j\) are origin and destination station fixed effects6, and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce orig and dest in the the model:

        +

        6 In this session, \(\delta_i\) and \(\delta_j\) are estimated as independent variables so their estimates are similar to interpret to those in \(\beta\). An alternative approach could be to model them as random effects in a multilevel framework.

        +
        m3 <- glm(
        +  'trips15 ~ straight_dist + total_up + total_down + orig + dest', 
        +  data = db_std,
        +  family = poisson
        +)
        +
        +

        And with our new model, we can have a look at how well it does at predicting the overall number of trips7:

        +

        7 Although, theoretically, we could also include simulations of the model in the plot to get a better sense of the uncertainty behind our model, in practice this seems troublesome. The problems most likely arise from the fact that many of the origin and destination binary variable coefficients are estimated with a great deal of uncertainty. This causes some of the simulation to generate extreme values that, when passed through the exponential term of the Poisson link function, cause problems. If anything, this is testimony of how a simple fixed effect model can sometimes lack accuracy and generate very uncertain estimates. A potential extension to work around these problems could be to fit a multilevel model with two specific levels beyond the trip-level: one for origin and another one for destination stations.

        +
        plot(
        +  density( m3$fitted.values ), 
        +  xlim = c(-100, max( db_std$trips15 )),
        +  ylim = c(0, max(c(
        +               max( density(m3$fitted.values)$y ), 
        +               max( density(db_std$trips15)$y )
        +               )
        +            )
        +   ),
        +  col = 'black',
        +  main = ''
        +)
        +lines(
        +  density( db_std$trips15 ), 
        +  col = 'red',
        +  main = ''
        +)
        +legend(
        +  'topright', 
        +  c('Predicted', 'Actual'),
        +  col = c('black', 'red'),
        +  lwd = 1
        +)
        +title( main = "Predictive check - Orig/dest FE Poisson model")
        -

        +
        +

        +
        +

        That looks significantly better, doesn’t it? In fact, our model now better accounts for the long tail where a few routes take a lot of trips. This is likely because the distribution of trips is far from random across stations and our origin and destination fixed effects do a decent job at accounting for that structure. However our model is still notably underpredicting less popular routes and overpredicting routes with above average number of trips. Maybe we should think about moving beyond a simple linear model.

        @@ -854,45 +890,48 @@

        The final extension is, in principle, always available but, in practice, it can be tricky to implement. The core idea is that your baseline model might not have the best measurement of the phenomena you want to account for. In our example, we can think of the distance between stations. So far, we have been including the distance measured “as the crow flies” between stations. Although in some cases this is a good approximation (particularly when distances are long and likely route taken is as close to straight as possible), in some cases like ours, where the street layout and the presence of elevation probably matter more than the actual final distance pedalled, this is not necessarily a safe assumption.

        As an exampe of this approach, we can replace the straight distance measurements for more refined ones based on the Google Maps API routes. This is very easy as all we need to do (once the distances have been calculated!) is to swap straight_dist for street_dist:

        -
        m4 <- glm(
        -  'trips15 ~ street_dist + total_up + total_down + orig + dest', 
        -  data=db_std,
        -  family=poisson
        -)
        +
        m4 <- glm(
        +  'trips15 ~ street_dist + total_up + total_down + orig + dest', 
        +  data = db_std,
        +  family = poisson
        +)

        And we can similarly get a sense of our predictive fitting with:

        -
        -
        plot(
        -  density(m4$fitted.values), 
        -  xlim=c(-100, max(db_std$trips15)),
        -  ylim=c(0, max(c(
        -               max(density(m4$fitted.values)$y), 
        -               max(density(db_std$trips15)$y)
        -               )
        -            )
        -   ),
        -  col='black',
        -  main=''
        -)
        -lines(
        -  density(db_std$trips15), 
        -  col='red',
        -  main=''
        -)
        -legend(
        -  'topright', 
        -  c('Predicted', 'Actual'),
        -  col=c('black', 'red'),
        -  lwd=1
        -)
        -title(main="Predictive check - Orig/dest FE Poisson model")
        +
        +
        plot(
        +  density( m4$fitted.values ), 
        +  xlim = c(-100, max( db_std$trips15)),
        +  ylim = c(0, max(c(
        +               max( density(m4$fitted.values)$y ), 
        +               max( density(db_std$trips15)$y )
        +               )
        +            )
        +   ),
        +  col = 'black',
        +  main = ''
        +)
        +lines(
        +  density( db_std$trips15 ), 
        +  col = 'red',
        +  main = ''
        +)
        +legend(
        +  'topright', 
        +  c('Predicted', 'Actual'),
        +  col = c('black', 'red'),
        +  lwd = 1
        +)
        +title( main = "Predictive check - Orig/dest FE Poisson model")
        -

        +
        +

        +
        +

        Hard to tell any noticeable difference, right? To see if there is any, we can have a look at the estimates obtained:

        -
        summary(m4)$coefficients['street_dist', ]
        +
        summary(m4)$coefficients['street_dist', ]
              Estimate     Std. Error        z value       Pr(>|z|) 
          -9.961619e-02   2.688731e-03  -3.704952e+01  1.828096e-300 
        @@ -900,20 +939,19 @@

        And compare this to that of the straight distances in the previous model:

        -
        summary(m3)$coefficients['straight_dist', ]
        +
        summary(m3)$coefficients['straight_dist', ]
              Estimate     Std. Error        z value       Pr(>|z|) 
          -7.820014e-02   2.683052e-03  -2.914596e+01  9.399407e-187 
        -

        As we can see, the differences exist but they are not massive. Let’s use this example to learn how to interpret coefficients in a Poisson model9. Effectively, these estimates can be understood as multiplicative effects. Since our model fits

        -

        \[ +

        As we can see, the differences exist but they are not massive. Let’s use this example to learn how to interpret coefficients in a Poisson model8. Effectively, these estimates can be understood as multiplicative effects. Since our model fits

        +

        8 See section 6.2 of Gelman and Hill (2006) for a similar treatment of these.

        \[ T_{ij} \sim Poisson (\exp^{X_{ij}\beta}) \]

        -

        we need to transform \(\beta\) through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is \(\beta_{street} = -0.0996\), but this needs to be translated through the exponential into \(e^{-0.0996} = 0.906\). In other words, since distance is expressed in standard deviations10, we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with \(e^{-0.0782} = 0.925\) for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m).

        -

    -
    -

    5.5 Predicting flows

    +

    we need to transform \(\beta\) through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is \(\beta_{street} = -0.0996\), but this needs to be translated through the exponential into \(e^{-0.0996} = 0.906\). In other words, since distance is expressed in standard deviations9, we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with \(e^{-0.0782} = 0.925\) for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m).

    +

    9 Remember the transformation at the very beginning.

    +5.5 Predicting flows

    So far we have put all of our modeling efforts in understanding the model we fit and improving such model so it fits our data as closely as possible. This is essential in any modelling exercise but should be far from a stopping point. Once we’re confident our model is a decent representation of the data generating process, we can start exploiting it. In this section, we will cover one specific case that showcases how a fitted model can help: out-of-sample forecasts.

    It is August 2015, and you have just started working as a data scientist for the bikeshare company that runs the San Francisco system. You join them as they’re planning for the next academic year and, in order to plan their operations (re-allocating vans, station maintenance, etc.), they need to get a sense of how many people are going to be pedalling across the city and, crucially, where they are going to be pedalling through. What can you do to help them?

    The easiest approach is to say “well, a good guess for how many people will be going between two given stations this coming year is how many went through last year, isn’t it?”. This is one prediction approach. However, you could see how, even if the same process governs over both datasets (2015 and 2016), each year will probably have some idiosyncracies and thus looking too closely into one year might not give the best possible answer for the next one. Ideally, you want a good stylized synthesis that captures the bits that stay constant over time and thus can be applied in the future and that ignores those aspects that are too particular to a given point in time. That is the rationale behind using a fitted model to obtain predictions.

    @@ -923,40 +961,53 @@

    where \(\hat{T_{ij}}\) is the predicted amount of trips between stations \(i\) and \(j\). RMSE is straightforward in R and, since we will use it a couple of times, let’s write a short function to make our lives easier:

    -
    rmse <- function(t, p){
    -  se <- (t - p)^2
    -  mse <- mean(se)
    -  rmse <- sqrt(mse)
    -  return(rmse)
    -}
    +
    rmse <- function(t, p){
    +  se <- (t - p)^2
    +  mse <- mean(se)
    +  rmse <- sqrt(mse)
    +  return(rmse)
    +}

    where t stands for the vector of true values, and p is the vector of predictions. Let’s give it a spin to make sure it works:

    -
    rmse_m4 <- rmse(db_std$trips16, m4$fitted.values)
    -rmse_m4
    +
    rmse_m4 <- rmse(db_std$trips16, m4$fitted.values)
    +rmse_m4
    [1] 256.2197
    -

    That means that, on average, predictions in our best model m4 are 256 trips off. Is this good? Bad? Worse? It’s hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year’s counts:11

    -
    -
    rmses <- data.frame(
    -  model=c(
    -    'OLS', 
    -    'Poisson', 
    -    'Poisson + FE', 
    -    'Poisson + FE + street dist.',
    -    'Trips-2015'
    -  ),
    -  RMSE=c(
    -  rmse(db_std$trips16, m1$fitted.values),
    -  rmse(db_std$trips16, m2$fitted.values),
    -  rmse(db_std$trips16, m3$fitted.values),
    -  rmse(db_std$trips16, m4$fitted.values),
    -  rmse(db_std$trips16, db_std$trips15)
    -  )
    -)
    -rmses
    +

    That means that, on average, predictions in our best model m4 are 256 trips off. Is this good? Bad? Worse? It’s hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year’s counts:[^05-flows-11]

    + +
    +
    +
    +
    + +
    +
    +

    Task

    +

    Can you create a single plot that displays the distribution of the predicted values of the five different ways to predict trips in 2016 and the actual counts of trips?

    +
    +
    +
    +
    +
    rmses <- data.frame(
    +  model=c(
    +    'OLS', 
    +    'Poisson', 
    +    'Poisson + FE', 
    +    'Poisson + FE + street dist.',
    +    'Trips-2015'
    +  ),
    +  RMSE=c(
    +  rmse(db_std$trips16, m1$fitted.values),
    +  rmse(db_std$trips16, m2$fitted.values),
    +  rmse(db_std$trips16, m3$fitted.values),
    +  rmse(db_std$trips16, m4$fitted.values),
    +  rmse(db_std$trips16, db_std$trips15)
    +  )
    +)
    +rmses
                            model     RMSE
     1                         OLS 323.6135
    @@ -968,15 +1019,14 @@ 

    The table is both encouraging and disheartning at the same time. On the one hand, all the modeling techniques covered above behave as we would expect: the baseline model displays the worst predicting power of all, and every improvement (except the street distances!) results in notable decreases of the RMSE. This is good news. However, on the other hand, all of our modelling efforts fall short of given a better guess than simply using the previous year’s counts. Why? Does this mean that we should not pay attention to modeling and inference? Not really. Generally speaking, a model is as good at predicting as it is able to mimic the underlying process that gave rise to the data in the first place. The results above point to a case where our model is not picking up all the factors that determine the amount of trips undertaken in a give route. This could be improved by enriching the model with more/better predictors, as we have seen above. Also, the example above seems to point to a case where those idiosyncracies in 2015 that the model does not pick up seem to be at work in 2016 as well. This is great news for our prediction efforts this time, but we have no idea why this is the case and, for all that matters, it could change the coming year. Besides the elegant quantification of uncertainty, the true advantage of a modeling approach in this context is that, if well fit, it is able to pick up the fundamentals that apply over and over. This means that, if next year we’re not as lucky as this one and previous counts are not good predictors but the variables we used in our model continue to have a role in determining the outcome, the data scientist should be luckier and hit a better prediction.

    -

    -
    -

    5.6 Questions

    +

    +5.6 Questions

    We will be using again the Madrid AirBnb dataset:

    -
    mad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')
    +
    mad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')
    Reading layer `madrid_abb' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' 
       using driver `GPKG'
     Simple feature collection with 18399 features and 16 fields
     Geometry type: POINT
    @@ -987,12 +1037,18 @@ 

    @@ -1012,40 +1068,23 @@

    -
    -
    -
      -
    1. EXERCISE: can you plot the route for the largest climb?↩︎

    2. -
    3. For a more elaborate introduction to PPC, have a look at Chapters 7 and 8.↩︎

    4. -
    5. The \(R^2\) of our model is around 2%↩︎

    6. -
    7. which they are not really, in light of the comparison between the black and red lines.↩︎

    8. -
    9. These principles are general and can be applied to pretty much any modeling exercise you run into. The specific approaches we take in this note relate to spatial interaction models↩︎

    10. -
    11. For an illustration of this, consider the amount of probability mass to the left of zero in the predictive checks above.↩︎

    12. -
    13. In this session, \(\delta_i\) and \(\delta_j\) are estimated as independent variables so their estimates are similar to interpret to those in \(\beta\). An alternative approach could be to model them as random effects in a multilevel framework.↩︎

    14. -
    15. Although, theoretically, we could also include simulations of the model in the plot to get a better sense of the uncertainty behind our model, in practice this seems troublesome. The problems most likely arise from the fact that many of the origin and destination binary variable coefficients are estimated with a great deal of uncertainty. This causes some of the simulation to generate extreme values that, when passed through the exponential term of the Poisson link function, cause problems. If anything, this is testimony of how a simple fixed effect model can sometimes lack accuracy and generate very uncertain estimates. A potential extension to work around these problems could be to fit a multilevel model with two specific levels beyond the trip-level: one for origin and another one for destination stations.↩︎

    16. -
    17. See section 6.2 of Gelman and Hill (2006) for a similar treatment of these.↩︎

    18. -
    19. Remember the transformation at the very beginning.↩︎

    20. -
    21. EXERCISE: can you create a single plot that displays the distribution of the predicted values of the five different ways to predict trips in 2016 and the actual counts of trips?↩︎

    22. -
    -
    - -

    - -
    + \ No newline at end of file diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-11-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-11-1.png index 60d80cd..229e975 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-11-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-12-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-12-1.png new file mode 100644 index 0000000..e129f1c Binary files /dev/null and b/docs/05-flows_files/figure-html/unnamed-chunk-12-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-13-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-13-1.png deleted file mode 100644 index 5016de5..0000000 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-13-1.png and /dev/null differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-14-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-14-1.png deleted file mode 100644 index f032576..0000000 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-14-1.png and /dev/null differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-15-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-15-1.png new file mode 100644 index 0000000..a52705c Binary files /dev/null and b/docs/05-flows_files/figure-html/unnamed-chunk-15-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-17-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-17-1.png index 19b2da4..d3796ed 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-17-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-18-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-18-1.png new file mode 100644 index 0000000..6e5a36d Binary files /dev/null and b/docs/05-flows_files/figure-html/unnamed-chunk-18-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-19-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-19-1.png deleted file mode 100644 index cbcb217..0000000 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-19-1.png and /dev/null differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-20-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-20-1.png index 4586627..1cbc1e4 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-20-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-22-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-22-1.png index cbe631e..3ebfbc3 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-22-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-22-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-24-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-24-1.png index ecbf019..054faa1 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-24-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-26-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-26-1.png index c09a9bc..d242064 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-26-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-26-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-28-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-28-1.png deleted file mode 100644 index 0a32e74..0000000 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-28-1.png and /dev/null differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-4-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-4-1.png new file mode 100644 index 0000000..f0d2aaf Binary files /dev/null and b/docs/05-flows_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-5-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-5-1.png index f0d2aaf..c3be2ec 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-5-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-6-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-6-1.png deleted file mode 100644 index c3be2ec..0000000 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-6-1.png and /dev/null differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-7-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-7-1.png new file mode 100644 index 0000000..3b186e4 Binary files /dev/null and b/docs/05-flows_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/docs/05-flows_files/figure-html/unnamed-chunk-9-1.png b/docs/05-flows_files/figure-html/unnamed-chunk-9-1.png index be4f0f5..ee329e5 100644 Binary files a/docs/05-flows_files/figure-html/unnamed-chunk-9-1.png and b/docs/05-flows_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/docs/06-spatial-econometrics.html b/docs/06-spatial-econometrics.html index baf71b2..9c8425e 100644 --- a/docs/06-spatial-econometrics.html +++ b/docs/06-spatial-econometrics.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 6  Spatial Econometrics - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    6  Spatial Econometrics

    +
    +

    6  Spatial Econometrics

    @@ -298,52 +303,39 @@

    - -
    + \ No newline at end of file diff --git a/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-5-1.png b/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-4-1.png similarity index 100% rename from docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-5-1.png rename to docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-4-1.png diff --git a/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-7-1.png b/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-7-1.png new file mode 100644 index 0000000..0634fc8 Binary files /dev/null and b/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-7-1.png differ diff --git a/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-8-1.png b/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-8-1.png deleted file mode 100644 index 3be6ccd..0000000 Binary files a/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-8-1.png and /dev/null differ diff --git a/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-10-1.png b/docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-9-1.png similarity index 100% rename from docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-10-1.png rename to docs/06-spatial-econometrics_files/figure-html/unnamed-chunk-9-1.png diff --git a/docs/07-multilevel-01.html b/docs/07-multilevel-01.html index ebd9f0f..5c0286f 100644 --- a/docs/07-multilevel-01.html +++ b/docs/07-multilevel-01.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 7  Multilevel Modelling - Part 1 - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    7  Multilevel Modelling - Part 1

    +
    +

    7  Multilevel Modelling - Part 1

    @@ -306,63 +315,61 @@

    Gelman and Hill (2006) provides an excellent and intuitive explanation of multilevel modelling and data analysis in general. Read Part 2A for a really good explanation of multilevel models. -
  • Multilevel Modelling (n.d.) is an useful online resource on multilevel modelling and is free!
  • +
  • +Gelman and Hill (2006) provides an excellent and intuitive explanation of multilevel modelling and data analysis in general. Read Part 2A for a really good explanation of multilevel models.
  • +
  • +Multilevel Modelling (n.d.) is an useful online resource on multilevel modelling and is free!
  • -
    -

    7.1 Dependencies

    -

    This chapter uses the following libraries which are listed in the [Dependency list] Section of Chapter 1:

    +

    +7.1 Dependencies

    +

    We will use the following dependencies

    -
    # Data manipulation, transformation and visualisation
    -library(tidyverse)
    -# Nice tables
    -library(kableExtra)
    -# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    -library(sf) 
    -# Spatial objects conversion
    -library(sp) 
    -# Thematic maps
    -library(tmap) 
    -# Colour palettes
    -library(RColorBrewer) 
    -# More colour palettes
    -library(viridis) # nice colour schemes
    -# Fitting multilevel models
    -library(lme4)
    -# Tools for extracting information generated by lme4
    -library(merTools)
    -# Exportable regression tables
    -library(jtools)
    -library(stargazer)
    -library(sjPlot)
    -
    -
    -
    -

    7.2 Data

    +
    # Data manipulation, transformation and visualisation
    +library(tidyverse)
    +# Nice tables
    +library(kableExtra)
    +# Spatial data manitulation
    +library(sf) 
    +# Thematic maps
    +library(tmap) 
    +# Colour palettes
    +library(viridis) 
    +# Fitting multilevel models
    +library(lme4)
    +# Tools for extracting information generated by lme4
    +library(merTools)
    +# Exportable regression tables
    +library(jtools)
    +

    +

    +7.2 Data

    For this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic attributes of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1

    -

    Let us read the data:

    +

    1 Read the file in R by executing read_tsv("data/mlm/readme.txt") . Ensure the library readr is installed before running read_tsv.

    Let us read the data:

    -
    # clean workspace
    -rm(list=ls())
    -# read data
    -oa_shp <- st_read("data/mlm/OA.shp")
    +
    # read data
    +oa_shp <- st_read("data/mlm/OA.shp")
    +
    +
    Reading layer `OA' from data source 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/OA.shp' 
    +  using driver `ESRI Shapefile'
    +Simple feature collection with 1584 features and 19 fields
    +Geometry type: MULTIPOLYGON
    +Dimension:     XY
    +Bounding box:  xmin: 332390.2 ymin: 379748.5 xmax: 345636 ymax: 397980.1
    +Projected CRS: Transverse_Mercator
    +

    We can now attach and visualise the structure of the data.

    -
    # attach data frame
    -attach(oa_shp)
    -
    -
    The following object is masked from package:viridis:
    -
    -    unemp
    -
    -
    # sort data by oa
    -oa_shp <- oa_shp[order(oa_cd),]
    -head(oa_shp)
    +
    # attach data frame
    +attach(oa_shp)
    +
    +# sort data by oa
    +oa_shp <- oa_shp[order(oa_cd),]
    +head(oa_shp)
    Simple feature collection with 6 features and 19 fields
     Geometry type: MULTIPOLYGON
    @@ -393,10 +400,8 @@ 

    -
    -

    -

    Fig. 1. Data Structure.

    -
    +

    +
    Fig. 1. Data Structure.

    The data are hierarchically structured: OAs nested within LSOAs; LSOAs nested within MSOAs; and, MSOAs nested within LADs. Observations nested within higher geographical units may be correlated.

    This is one type of hierarchical structure. There is a range of data structures:

    @@ -414,48 +419,56 @@

    -
    # mean of nested OAs within LSOAs and MSOAs
    -lsoa_cd %>% table() %>%
    -  mean() %>%
    -  round(, 2)
    +
    # mean of nested OAs within LSOAs and MSOAs
    +lsoa_cd %>% table() %>%
    +  mean() %>%
    +  round(, 2)
    [1] 5
    -
    msoa_cd %>% table() %>%
    -  mean() %>%
    -  round(, 2)
    +
    msoa_cd %>% table() %>%
    +  mean() %>%
    +  round(, 2)
    [1] 26
    -
    # number of OAs nested within LSOAs and MSOAs
    -lsoa_cd %>% table() %>%
    -  sort() %>%
    -  plot()
    +
    # number of OAs nested within LSOAs and MSOAs
    +lsoa_cd %>% table() %>%
    +  sort() %>%
    +  plot()
    -

    +
    +

    +
    +
    -
    msoa_cd %>% table() %>%
    -  sort() %>%
    -  plot()
    +
    msoa_cd %>% table() %>%
    +  sort() %>%
    +  plot()
    -

    +
    +

    +
    +

    -
    -
    -

    7.3 Modelling

    +

    +7.3 Modelling

    We should now be persuaded that ignoring the hierarchical structure of data may be a major issue. Let us now use a simple example to understand the intuition of multilevel model using the census data. We will seek to understand the spatial distribution of the proportion of population in unemployment in Liverpool, particularly why and where concentrations in this proportion occur. To illustrate the advantages of taking a multilevel modelling approach, we will start by estimating a linear regression model and progressively building complexity. We will first estimate a model and then explain the intuition underpinning the process. We will seek to gain a general understanding of multilevel modelling. If you are interested in the statistical and mathemathical formalisation of the underpinning concepts, please refer to Gelman and Hill (2006).

    We first need to want to understand our dependent variable: its density ditribution;

    -
    ggplot(data = oa_shp) +
    -geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = unemp)) +
    -   theme_classic()
    +
    ggplot(data = oa_shp) +
    +  geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = unemp)) +
    +   theme_plot_tufte()
    -

    +
    +

    +
    +
    -
    summary(unemp)
    +
    summary(unemp)
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     0.00000 0.05797 0.10256 0.11581 0.16129 0.50000 
    @@ -463,26 +476,29 @@

    -
    # ensure geometry is valid
    -oa_shp = sf::st_make_valid(oa_shp)
    -
    -# create a map
    -legend_title = expression("% unemployment")
    -map_oa = tm_shape(oa_shp) +
    -  tm_fill(col = "unemp", title = legend_title, palette = magma(256, begin = 0.25, end = 1), style = "cont") + 
    -  tm_borders(col = "white", lwd = .01)  + 
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    -map_oa
    +
    # ensure geometry is valid
    +oa_shp = sf::st_make_valid(oa_shp)
    +
    +# create a map
    +legend_title = expression("% unemployment")
    +map_oa = tm_shape(oa_shp) +
    +  tm_fill(col = "unemp", title = legend_title, palette = magma(256, begin = 0.25, end = 1), style = "cont") + 
    +  tm_borders(col = "white", lwd = .01)  + 
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    +map_oa
    -

    +
    +

    +
    +

    Let us look at those areas:

    -
    # high %s
    -oa_shp %>% filter(unemp > 0.2) %>% 
    -  dplyr::select(oa_cd, pop, unemp) 
    +
    # high %s
    +oa_shp %>% filter(unemp > 0.2) %>% 
    +  dplyr::select(oa_cd, pop, unemp) 
    Simple feature collection with 203 features and 3 fields
     Geometry type: POLYGON
    @@ -503,16 +519,16 @@ 

    -

    7.3.1 Baseline Linear Regression Model

    +

    +7.3.1 Baseline Linear Regression Model

    Now let us estimate a simple linear regression model with the intercept only:

    -
    # specify a model equation
    -eq1 <- unemp ~ 1
    -model1 <- lm(formula = eq1, data = oa_shp)
    -
    -# estimates
    -summary(model1)
    +
    # specify a model equation
    +eq1 <- unemp ~ 1
    +model1 <- lm(formula = eq1, data = oa_shp)
    +
    +# estimates
    +summary(model1)
    
     Call:
    @@ -533,28 +549,24 @@ 

    \[y_{i} = \beta_{0} + e_{i}\] where \(y_{i}\) represents the proportion of the unemployed resident population in the OA \(i\); \(\beta_{0}\) is the regression intercept and measures the average proportion of the unemployed resident population across OAs; and, \(e_{i}\) is the error term. But how do we deal with the hierarchical structure of the data?

    -
    -

    7.3.1.1 Limitations

    +

    +7.3.1.1 Limitations

    Before looking at the answer, let’s first understand some of the key limitations of the linear regression model to handle the hierarchical structure of data. A key limitation of the linear regression model is that it only captures average relationships in the data. It does not capture variations in the relationship between variables across areas or groups. Another key limitation is that the linear regression model can capture associations at either macro or micro levels, but it does not simultaneously measure their interdependencies.

    To illustrate this, let us consider the regression intercept. It indicates that the average percentage of unemployed population at the OA level is 0.12 but this model ignores any spatial clustering ie. the percentage of unemployed population tends to be similar across OAs nested within a same LSOA or MSOA. A side effect of ignoring this is that our standard errors are biased, and thus claims about statistical significance based on them would be misleading. Additionally, this situation also means we cannot explore variations in the percentage of unemployed population across LSOAs or MSOAs ie. how the percentage of unemployed population may be dependent on various contextual factors at these geographical scales.

    -
    -
    -

    7.3.1.2 Fixed Effect Approach

    +

    +7.3.1.2 Fixed Effect Approach

    An alternative approach is to adopt a fixed effects approach, or no-pooling model; that is, adding dummy variables indicating the group classification into the regression model eg. the way OAs is nested within LSOAs (or MSOAs). This approach has limitations. First, there is high risk of overfitting. The number of groups may be too large, relative to the number of observations. Second, the estimation of multiple parameters may be required so that measuring differences between groups may be challenging. Third, a fixed effects approach does not allow including group-level explanatory variables. You can try fitting a linear regression model extending our estimated model to include dummy variables for individual LSOAs (and/or MSOAs) so you can compare this to the multilevel model below.

    An alternative is fitting separate linear regression models for each group. This approach is not always possible if there are groups with small sizes.

    -
    -
    -

    -
    -

    7.4 Multilevel Modelling: Random Intercept Model

    +

    +7.4 Multilevel Modelling: Random Intercept Model

    We use multilevel modelling to account for the hierarchical nature of the data by explicitly recognising that OAs are nested within LSOAs and MSOAs. Multilevel models can easily be estimated using in R using the package lme4. We implement an two-level model to allow for variation across LSOAs. We estimate an only intercept model allowing for variation across LSOAs. In essence, we are estimating a model with varying intercept coefficient by LSOA. As you can see in the code chunk below, the equation has an additional component. This is the group component or LSOA effect. The (1 | lsoa_cd) means that we are allowing the intercept, represented by 1, to vary by LSOA.

    -
    # specify a model equation
    -eq2 <- unemp ~ 1 + (1 | lsoa_cd)
    -model2 <- lmer(eq2, data = oa_shp)
    -
    -# estimates
    -summary(model2)
    +
    # specify a model equation
    +eq2 <- unemp ~ 1 + (1 | lsoa_cd)
    +model2 <- lmer(eq2, data = oa_shp)
    +
    +# estimates
    +summary(model2)
    Linear mixed model fit by REML ['lmerMod']
     Formula: unemp ~ 1 + (1 | lsoa_cd)
    @@ -577,17 +589,20 @@ 

    -
    # specify a model equation
    -eq3 <- unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)
    -model3 <- lmer(eq3, data = oa_shp)
    -
    -# estimates
    -summary(model3)
    +
    # specify a model equation
    +eq3 <- unemp ~ 1 + (1 | msoa_cd/lsoa_cd)
    +model3 <- lmer(eq3, data = oa_shp)
    +
    +# estimates
    +summary(model3)
    Linear mixed model fit by REML ['lmerMod']
    -Formula: unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)
    +Formula: unemp ~ 1 + (1 | msoa_cd/lsoa_cd)
        Data: oa_shp
     
     REML criterion at convergence: -4529.3
    @@ -597,11 +612,11 @@ 

    -
    -

    -

    Fig. 2. Variation of observations around their level 1 group mean.

    -
    +

    +
    Fig. 2. Variation of observations around their level 1 group mean.

    -
    -

    -

    Fig. 3. Variation of level 1 group mean around their level 2 group mean.

    -
    +

    +
    Fig. 3. Variation of level 1 group mean around their level 2 group mean.
    -
    -

    -

    Fig. 4. Grand mean.

    -
    +

    +
    Fig. 4. Grand mean.

    More formally, we first estimated the simplest regression model which is an intercept-only model and equivalent to the sample mean (i.e. the fixed part of the model):

    \[y_{ijk} = \mu + e_{ijk}\] and then we made the random part of the model (\(e_{ijk}\)) more complex to account for the hierarchical structure of the data by estimating the following three-level regression model:

    @@ -633,11 +642,11 @@

    \(y_{ijk}\) represents the proportion of unemployed population in OA \(i\) nested within LSOA \(j\) and MSOA \(k\); \(\mu\) represents the sample mean and the fixed part of the model; \(e_{ijk}\) is the deviation of an observation from its LSOA mean; \(u_{ij.}\) is the deviation of the LSOA mean from its MSOA mean; \(u_{i..}\) is the deviation of the MSOA mean from the fixed part of the model \(\mu\). Conceptually, this model is decomposing the variance of the model in terms of the hierarchical structure of the data. It is partitioning the observation’s residual into three parts or variance components. These components measure the relative extent of variation of each hierarchical level ie. LSOA, MSOA and grand means. To estimate the set of residuals, they are assumed to follow a normal distribution and are obtained after fitting the model and are based on the estimates of the model parameters (i.e. intercept and variances of the random parameters).

    Let’s now return to our three-level model (reported again below), we see that the intercept or fixed part of the model is the same as for the linear regression. The multilevel model reports greater standard errors. Multilevel models capture the hierarchical structure of the data and thus more precisely estimate the standard errors for our parameters.

    -
    # report model 3
    -summary(model3)
    +
    # report model 3
    +summary(model3)
    Linear mixed model fit by REML ['lmerMod']
    -Formula: unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)
    +Formula: unemp ~ 1 + (1 | msoa_cd/lsoa_cd)
        Data: oa_shp
     
     REML criterion at convergence: -4529.3
    @@ -647,165 +656,219 @@ 

    -

    7.4.1 Interpretation

    +

    +7.4.1 Interpretation

    Fixed effects

    We start by examining the fixed effects or estimated model averaging over LSOAs and MSOAs, \(y_{ijk} = 0.115288\) which can also be called by executing:

    -
    fixef(model3)
    +
    fixef(model3)
    (Intercept) 
       0.1152881 
    -

    Th estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as 0.115288 and is statistically significant at 5% significance.

    +

    Th estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as 0.1152881.

    Random effects

    -

    The set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The lsoa_cd, msoa_cd and Residual estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is 0.0007603, 0.0020735 and 0.0025723, respectively.

    -
    -
    -

    7.4.2 Variance Partition Coefficient (VPC)

    -

    The purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.2 For our case, the VPC at the LSOA level indicates that 14% of the variation in percentage of unemployed resident population across OAs can be explained by differences across LSOAs. What is the VPC at the MSOA level?

    +

    The set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The lsoa_cd:msoa_cd, msoa_cd and Residual estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is 0.0007603, 0.0020735 and 0.0025723, respectively.

    +

    +7.4.2 Variance Partition Coefficient (VPC)

    +

    The purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.2 For our case, the VPC at the MSOA level indicates that 38% of the variation in percentage of unemployed resident population across OAs can be explained by differences across MSOAs.

    +

    2 The VPC is equal to the intra-class correlation coefficient which is the correlation between the observations of the dependent variable selected randomly from the same group. For instance, if the VPC is 0.1, we would say that 10% of the variation is between groups and 90% within. The correlation between randomly chosen pairs of observations belonging to the same group is 0.1.

    +
    +
    +
    + +
    +
    +

    Task What is the VPC at the LSOA level?

    +
    +
    +
    +
    -
    vpc_lsoa <- 0.0007603 / (0.0007603 + 0.0020735 + 0.0025723)
    -vpc_lsoa * 100
    +
    vpc_msoa <- 0.0020735 / (0.0007603 + 0.0020735 + 0.0025723)
    +vpc_msoa * 100
    -
    [1] 14.06374
    +
    [1] 38.35482

    You can also obtain the VPC by executing:

    -
    summ(model3)
    +
    summ(model3)
    - - - - - - - - - - - - - - - - -
    Observations 1584
    Dependent variable unemp
    Type Mixed effects linear regression
    - - - - - - - - - - - - - - - - - - -
    AIC -4521.26
    BIC -4499.79
    Pseudo-R² (fixed effects) 0.00
    Pseudo-R² (total) 0.52
    - - - - - - - - - - - - - - - - - - - - - - -
    Fixed Effects
    Est. S.E. t val. d.f. p
    (Intercept) 0.12 0.01 18.63 59.98 0.00
    - p values calculated using Kenward-Roger standard errors and d.f.
    - - - - - - - - +
    +
    Random Effects
    Group Parameter Std. Dev.
    + + + + + + + + + + + + +
    Observations1584
    Dependent variableunemp
    TypeMixed effects linear regression
    +
    +
    + + + + + + + + + + + + + + + + + +
    AIC-4521.26
    BIC-4499.79
    Pseudo-R² (fixed effects)0.00
    Pseudo-R² (total)0.52
    +
    +
    + ++++++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +Fixed Effects +
    Est.S.E.t val.d.f.p
    (Intercept)0.120.0118.6359.980.00
    + p values calculated using Kenward-Roger standard errors and d.f.
    +
    +
    + +++++ + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + -
    +Random Effects +
    GroupParameterStd. Dev.
    lsoa_cd (Intercept) 0.03
    msoa_cd (Intercept) 0.05
    Residual 0.05
    lsoa_cd:msoa_cd(Intercept)0.03
    msoa_cd(Intercept)0.05
    Residual0.05
    - - - - - - - - +
    Grouping Variables
    Group # groups ICC
    +
    +
    + +++++ + + + + + + + + + + - - - - - - - - - - + + + + + + + + + +
    +Grouping Variables +
    Group# groupsICC
    lsoa_cd 298 0.14
    msoa_cd 61 0.38
    lsoa_cd:msoa_cd2980.14
    msoa_cd610.38
    -
    -
    -

    7.4.3 Uncertainty of Estimates

    +
    +

    +7.4.3 Uncertainty of Estimates

    You may have noticed that lme4 does not provide p-values, because of various reasons as explained by Doug Bates, one of the author of lme4. These explanations mainly refer to the complexity of dealing with varying sample sizes at a given hierarchical level. The number of observations at each hierarchical level varies across individual groupings (i.e. LSOA or MSOA). It may even be one single observation. This has implications for the distributional assumptions, denominator degrees of freedom and how to approximate a “best” solution. Various approaches exist to compute the statistical significance of estimates. We use the confint function available within lme4 to obtain confidence intervals.

    -
    confint(model3, level = 0.95)
    +
    confint(model3, level = 0.95)
    Computing profile confidence intervals ...
    @@ -818,71 +881,109 @@

    -

    7.4.4 Assessing Group-level Variation

    +

    +7.4.4 Assessing Group-level Variation

    Estimated regression coefficients

    In multilevel modelling, our primary interest is in knowing differences across groups. To visualise the estimated model within each group (ie. LSOA and MSOA), we type:

    -
    coef_m3 <- coef(model3)
    -head(coef_m3$lsoa_cd,5)
    +
    coef_m3 <- coef(model3)
    +head(coef_m3$lsoa_cd,5)
    -
              (Intercept)
    -E01006512  0.09915456
    -E01006513  0.09889615
    -E01006514  0.09297051
    -E01006515  0.09803754
    -E01006518  0.09642939
    +
                        (Intercept)
    +E01006512:E02001377  0.09915456
    +E01006513:E02006932  0.09889615
    +E01006514:E02001383  0.09297051
    +E01006515:E02001383  0.09803754
    +E01006518:E02001390  0.09642939
    +
    +
    +

    The results indicate that the estimated regression line is \(y = 0.09915456\) for LSOA E01006512 within MSOA E02001377; \(y = 0.09889615\) for LSOA E01006513 within MSOA E02006932 and so forth.

    + +
    +
    +
    +
    + +
    +
    +

    Task Try getting the estimated model within each MSOA.

    -

    The results indicate that the estimated regression line is \(y = 0.09915456\) for LSOA E01006512; \(y = 0.09889615\) for LSOA E01006513 and so forth. Try getting the estimated model within each MSOA.

    -

    Random effects

    +
    +

    Random effects

    We can look at the estimated group-level (or LSOA-level and MSOA-level) errors; that is, random effects:

    -
    ranef_m3 <- ranef(model3)
    -head(ranef_m3$lsoa_cd, 5)
    +
    ranef_m3 <- ranef(model3)
    +head(ranef_m3$lsoa_cd, 5)
    -
              (Intercept)
    -E01006512 -0.01613353
    -E01006513 -0.01639194
    -E01006514 -0.02231758
    -E01006515 -0.01725055
    -E01006518 -0.01885870
    +
                        (Intercept)
    +E01006512:E02001377 -0.01613353
    +E01006513:E02006932 -0.01639194
    +E01006514:E02001383 -0.02231758
    +E01006515:E02001383 -0.01725055
    +E01006518:E02001390 -0.01885870
    -

    Group-level errors indicate how much the intercept is shifted up or down in particular groups (ie. LSOAs or MSOAs). Thus, for example, in LSOA E01006512, the estimated intercept is -0.01613353 lower than average, so that the regression line is (0.1152881 - 0.01613353) = 0.09915457 which is what we observed from the call to coef().

    +

    Group-level errors indicate how much the intercept is shifted up or down in particular groups (ie. LSOAs or MSOAs). Thus, for example, in LSOA E01006512, the estimated intercept is -0.01613353 lower than average, so that the regression line is (0.1152881 - 0.01613353) = 0.09915457 which is what we observed from the call to coef().

    We can also obtain group-level errors (random effects) by using a simulation approach, labelled “Empirical Bayes” and discussed here. To this end, we run:

    -
    # obtain estimates
    -merTools::REsim(model3) %>% head(10)
    +
    # obtain estimates
    +merTools::REsim(model3) %>% 
    +  head(10)
    -
       groupFctr   groupID        term         mean       median          sd
    -1    lsoa_cd E01006512 (Intercept) -0.016090933 -0.016279825 0.020134603
    -2    lsoa_cd E01006513 (Intercept) -0.015635892 -0.016193114 0.019647670
    -3    lsoa_cd E01006514 (Intercept) -0.022289829 -0.022374465 0.018657688
    -4    lsoa_cd E01006515 (Intercept) -0.018205227 -0.017420404 0.018735274
    -5    lsoa_cd E01006518 (Intercept) -0.017721752 -0.016370427 0.019606654
    -6    lsoa_cd E01006519 (Intercept) -0.015710212 -0.015621840 0.009397891
    -7    lsoa_cd E01006520 (Intercept) -0.023901618 -0.023790192 0.018551656
    -8    lsoa_cd E01006521 (Intercept)  0.007266539  0.006503922 0.019235318
    -9    lsoa_cd E01006522 (Intercept)  0.019576849  0.018556373 0.019585302
    -10   lsoa_cd E01006523 (Intercept)  0.004543610  0.002947114 0.019247207
    +
             groupFctr             groupID        term         mean       median
    +1  lsoa_cd:msoa_cd E01006512:E02001377 (Intercept) -0.017347076 -0.018066461
    +2  lsoa_cd:msoa_cd E01006513:E02006932 (Intercept) -0.018159145 -0.019448281
    +3  lsoa_cd:msoa_cd E01006514:E02001383 (Intercept) -0.024598753 -0.028318987
    +4  lsoa_cd:msoa_cd E01006515:E02001383 (Intercept) -0.019774628 -0.021529676
    +5  lsoa_cd:msoa_cd E01006518:E02001390 (Intercept) -0.019300622 -0.019234412
    +6  lsoa_cd:msoa_cd E01006519:E02001402 (Intercept) -0.015437731 -0.015183404
    +7  lsoa_cd:msoa_cd E01006520:E02001389 (Intercept) -0.024036792 -0.023435811
    +8  lsoa_cd:msoa_cd E01006521:E02001398 (Intercept)  0.006797833  0.007696860
    +9  lsoa_cd:msoa_cd E01006522:E02001394 (Intercept)  0.019526516  0.020374198
    +10 lsoa_cd:msoa_cd E01006523:E02001398 (Intercept)  0.002772157  0.003187283
    +           sd
    +1  0.01710357
    +2  0.02214808
    +3  0.02184431
    +4  0.02027123
    +5  0.02178862
    +6  0.01030971
    +7  0.02169687
    +8  0.01897940
    +9  0.02022446
    +10 0.01896082

    The results contain the estimated mean, median and standard deviation for the intercept within each group (e.g. LSOA). The mean estimates are similar to those obtained from ranef with some small differences due to rounding.

    To gain an undertanding of the general pattern of the random effects, we can use caterpillar plots via plotREsim - reported below. The plot on the right shows the estimated random effects for each MSOA and their respective interval estimate. Note that random effects are on average zero, represented by the red horizontal line. Intervals that do not include zero are in bold. Also note that the width of the confidence interval depends on the standard error of the respective residual estimate, which is inversely related to the size of the sample. The residuals represent an observation departures from the grand mean, so an observation whose confidence interval does not overlap the line at zero (representing the mean proportion of unemployed population across all areas) is said to differ significantly from the average at the 5% level.

    -
    # plot
    -plotREsim(REsim(model3)) 
    +
    # plot
    +plotREsim(REsim(model3)) 
    -

    +
    +

    +
    -

    Focusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton. What districts do we have at the other extreme?

    -
    -
    re <- REsim(model3)
    -oa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp) %>%
    -    filter(as.character(msoa_cd) == "E02001387" | as.character(msoa_cd) == "E02001393")
    +
    +

    Focusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. These are the dots below the horizontal red line. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton.

    + +
    +
    +
    +
    + +
    +
    +

    Task What districts do we have at the other extreme? Have a go at identifying them.

    +
    +
    +
    +
    +
    re <- REsim(model3)
    +oa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp) %>%
    +    filter(as.character(msoa_cd) == "E02001387" | as.character(msoa_cd) == "E02001393")
    Simple feature collection with 49 features and 3 fields
     Geometry type: POLYGON
    @@ -905,11 +1006,11 @@ 

    -
    # read data
    -msoa_shp <- st_read("data/mlm/MSOA.shp")
    +
    # read data
    +msoa_shp <- st_read("data/mlm/MSOA.shp")
    Reading layer `MSOA' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/mlm/MSOA.shp' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/MSOA.shp' 
       using driver `ESRI Shapefile'
     Simple feature collection with 61 features and 17 fields
     Geometry type: MULTIPOLYGON
    @@ -917,41 +1018,43 @@ 

    # create a dataframe for MSOA-level random effects
    -re_msoa <- re %>% filter(groupFctr == "msoa_cd")
    -str(re_msoa)

    +
    # create a dataframe for MSOA-level random effects
    +re_msoa <- re %>% filter(groupFctr == "msoa_cd")
    +str(re_msoa)
    'data.frame':   61 obs. of  6 variables:
      $ groupFctr: chr  "msoa_cd" "msoa_cd" "msoa_cd" "msoa_cd" ...
      $ groupID  : chr  "E02001347" "E02001348" "E02001349" "E02001350" ...
      $ term     : chr  "(Intercept)" "(Intercept)" "(Intercept)" "(Intercept)" ...
    - $ mean     : num  -0.01306 -0.02417 -0.03594 0.00455 0.02435 ...
    - $ median   : num  -0.01421 -0.02405 -0.03499 0.00161 0.02427 ...
    - $ sd       : num  0.0356 0.0308 0.032 0.0306 0.0152 ...
    + $ mean : num -0.01044 -0.02588 -0.0287 0.00515 0.02168 ... + $ median : num -0.01136 -0.02427 -0.02754 0.00583 0.02144 ... + $ sd : num 0.0323 0.0319 0.0332 0.0324 0.0186 ...

    -
    # merge data
    -msoa_shp <- merge(x = msoa_shp, y = re_msoa, by.x = "MSOA_CD", by.y = "groupID")
    +
    # merge data
    +msoa_shp <- merge(x = msoa_shp, y = re_msoa, by.x = "MSOA_CD", by.y = "groupID")

    Now we can create our map:

    -
    # ensure geometry is valid
    -msoa_shp = sf::st_make_valid(msoa_shp)
    -
    -# create a map
    -legend_title = expression("MSOA-level residuals")
    -map_msoa = tm_shape(msoa_shp) +
    -  tm_fill(col = "mean", title = legend_title, palette = magma(256, begin = 0, end = 1), style = "cont") + 
    -  tm_borders(col = "white", lwd = .01)  + 
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    -map_msoa
    +
    # ensure geometry is valid
    +msoa_shp = sf::st_make_valid(msoa_shp)
    +
    +# create a map
    +legend_title = expression("MSOA-level residuals")
    +map_msoa = tm_shape(msoa_shp) +
    +  tm_fill(col = "mean", title = legend_title, palette = magma(256, begin = 0, end = 1), style = "cont") + 
    +  tm_borders(col = "white", lwd = .01)  + 
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    +map_msoa
    -

    +
    +

    +
    +
    -
    -
    -

    7.4.5 Adding Individual-level Predictors

    +

    +7.4.5 Adding Individual-level Predictors

    In this example, \(\mu\) represents the sample mean but it could include a collection of independent variables or predictors. To explain the logic, we will assume that unemployment is strongly associated to long-term illness. We could expect that long-term illness (lt_ill) will reduce the chances of working and therefore being unemployed. Note that our focus is on the relationship, not on establishing causation. Specifically we want to estimate the relationship between unemployment and long-term illness and we are interested in variations in OA-level unemployment by MSOAs so we will estimate the following two-level model:

    OA-level:

    \[y_{ij} = \beta_{0j} + \beta_{1}x_{ij} + e_{ij}\] MSOA-level:

    @@ -959,15 +1062,15 @@

    \[y_{ij} = (\beta_{0} + u_{0j}) + \beta_{1}x_{ij} + e_{ij}\] where \(y\) the proportion of unemployed population in OA \(i\) within MSOA \(j\); \(\beta_{0}\) is the fixed intercept (averaging over all MSOAs); \(u_{0j}\) represents the MSOA-level residuals or random effects; \(\beta_{0}\) and \(u_{0j}\) together represent the varying-intercept; \(\beta_{1}\) is the slope coefficient; \(x_{ij}\) represents the percentage of long-term illness population; and, \(e_{ij}\) is the individual-level residuals.

    We estimate the model executing:

    -
    # change to proportion
    -oa_shp$lt_ill <- lt_ill/100
    -
    -# specify a model equation
    -eq4 <- unemp ~ lt_ill + (1 | msoa_cd)
    -model4 <- lmer(eq4, data = oa_shp)
    -
    -# estimates
    -summary(model4)
    +
    # change to proportion
    +oa_shp$lt_ill <- lt_ill/100
    +
    +# specify a model equation
    +eq4 <- unemp ~ lt_ill + (1 | msoa_cd)
    +model4 <- lmer(eq4, data = oa_shp)
    +
    +# estimates
    +summary(model4)
    Linear mixed model fit by REML ['lmerMod']
     Formula: unemp ~ lt_ill + (1 | msoa_cd)
    @@ -997,7 +1100,7 @@ 

    Fixed effects: model averaging over MSOAs

    -
    fixef(model4)
    +
    fixef(model4)
    (Intercept)      lt_ill 
      0.04681959  0.29588110 
    @@ -1006,8 +1109,8 @@

    yields an estimated regression line in an average McSOA: \(y = 0.04681959 + 0.29588110x\)

    Random effects: MSOA-level errors

    -
    ranef_m4 <- ranef(model4)
    -head(ranef_m4$msoa_cd, 5)
    +
    ranef_m4 <- ranef(model4)
    +head(ranef_m4$msoa_cd, 5)
               (Intercept)
     E02001347 -0.017474815
    @@ -1017,15 +1120,79 @@ 

    -

    yields an estimated intercept for MSOA E02001347 which is 0.017474815 lower than the average with a regression line: (0.04681959 - 0.017474815) + 0.29588110x = 0.02934478 + 0.29588110x. You can confirm this by looking at the estimated model within each MSOA by executing (remove the # sign):

    +

    yields an estimated intercept for MSOA E02001347 which is 0.017474815 lower than the average with a regression line: (0.04681959 - 0.017474815) + 0.29588110x = 0.02934478 + 0.29588110x. You can confirm this by looking at the estimated model within each MSOA by executing on the first row:

    -
    #coef(model4)
    +
    coef(model4) %>% head(n = 5 )
    +
    +
    $msoa_cd
    +            (Intercept)    lt_ill
    +E02001347  0.0293447796 0.2958811
    +E02001348  0.0256157871 0.2958811
    +E02001349  0.0243502820 0.2958811
    +E02001350  0.0432797257 0.2958811
    +E02001351  0.0553224074 0.2958811
    +E02001352  0.0636246817 0.2958811
    +E02001353  0.0160357811 0.2958811
    +E02001354  0.0581675090 0.2958811
    +E02001355  0.0528556223 0.2958811
    +E02001356  0.1061228409 0.2958811
    +E02001357  0.0582394764 0.2958811
    +E02001358  0.0740589539 0.2958811
    +E02001359  0.0174543833 0.2958811
    +E02001360  0.0715947302 0.2958811
    +E02001361  0.0466345080 0.2958811
    +E02001362  0.0160157652 0.2958811
    +E02001363  0.0815677365 0.2958811
    +E02001364  0.0934291622 0.2958811
    +E02001365  0.0919597741 0.2958811
    +E02001366  0.0620614209 0.2958811
    +E02001367  0.0030188157 0.2958811
    +E02001368  0.0808079877 0.2958811
    +E02001369  0.0632672806 0.2958811
    +E02001370  0.1335873521 0.2958811
    +E02001371  0.0515952786 0.2958811
    +E02001372  0.0309188138 0.2958811
    +E02001373  0.0545884863 0.2958811
    +E02001374  0.1039777893 0.2958811
    +E02001375  0.0409780838 0.2958811
    +E02001376  0.0964558147 0.2958811
    +E02001377  0.0558567086 0.2958811
    +E02001378  0.0241577873 0.2958811
    +E02001380  0.0046345234 0.2958811
    +E02001381  0.0711500934 0.2958811
    +E02001382  0.0064505905 0.2958811
    +E02001383  0.0742504417 0.2958811
    +E02001384  0.0490214750 0.2958811
    +E02001385  0.1707802796 0.2958811
    +E02001386  0.0336177791 0.2958811
    +E02001387 -0.0007218010 0.2958811
    +E02001388  0.0125049014 0.2958811
    +E02001389  0.0711118539 0.2958811
    +E02001390  0.0805482208 0.2958811
    +E02001391  0.0417458225 0.2958811
    +E02001392 -0.0074952916 0.2958811
    +E02001393 -0.0051402516 0.2958811
    +E02001394  0.0181501721 0.2958811
    +E02001395  0.0009387908 0.2958811
    +E02001396  0.0521380692 0.2958811
    +E02001397 -0.0006698249 0.2958811
    +E02001398  0.0197886833 0.2958811
    +E02001399  0.0030131040 0.2958811
    +E02001400  0.0274024412 0.2958811
    +E02001401 -0.0043446188 0.2958811
    +E02001402  0.0074558647 0.2958811
    +E02001403  0.0539235547 0.2958811
    +E02001404  0.0647550886 0.2958811
    +E02001405  0.0903509760 0.2958811
    +E02006932  0.0310245337 0.2958811
    +E02006933  0.0276019142 0.2958811
    +E02006934  0.0350623557 0.2958811
    +

    Fixed effect correlations

    In the bottom of the output, we have the correlations between the fixed-effects estimates. In our example, it refers to the correlation between \(\beta_{0}\) and \(\beta_{1}\). It is negative indicating that in MSOAs where the relationship between unemployment and long-term illness is greater, as measured by \(\beta_{1}\), the average proportion of unemployed people tends to be smaller, as captured by \(\beta_{0}\).

    -

    -
    -

    7.4.6 Adding Group-level Predictors

    +

    +7.4.6 Adding Group-level Predictors

    We can also add group-level predictors. We use the formulation:

    OA-level:

    \[y_{ij} = \beta_{0j} + \beta_{1}x_{ij} + e_{ij}\]

    @@ -1033,28 +1200,24 @@

    \[\beta_{0j} = \beta_{0} + \gamma_{1}m_{j} + u_{0j}\]

    where \(x_{ij}\) is the OA-level proportion of population suffering long-term illness and \(m_{j}\) is the MSOA-level proportion of male population. We first need to create this group-level predictor:

    -
    # detach OA shp and attach MSOA shp
    -detach(oa_shp)
    -attach(msoa_shp)
    -
    -
    The following object is masked from package:viridis:
    -
    -    unemp
    -
    -
    # group-level predictor
    -msoa_shp$pr_male <- males/pop
    -
    -# remove geometries
    -msoa_df <- `st_geometry<-`(msoa_shp, NULL)
    -
    -# select variables
    -msoa_df <- msoa_df %>% dplyr::select(MSOA_CD, pop, pr_male)
    -
    -# merge data sets
    -oa_shp <- merge(x=oa_shp, y=msoa_df, by.x = "msoa_cd", by.y="MSOA_CD")
    -
    -# inspect data
    -head(oa_shp[1:10, c("msoa_cd", "oa_cd", "unemp", "pr_male")])
    +
    # detach OA shp and attach MSOA shp
    +detach(oa_shp)
    +attach(msoa_shp)
    +
    +# group-level predictor
    +msoa_shp$pr_male <- males/pop
    +
    +# remove geometries
    +msoa_df <- `st_geometry<-`(msoa_shp, NULL)
    +
    +# select variables
    +msoa_df <- msoa_df %>% dplyr::select(MSOA_CD, pop, pr_male)
    +
    +# merge data sets
    +oa_shp <- merge(x=oa_shp, y=msoa_df, by.x = "msoa_cd", by.y="MSOA_CD")
    +
    +# inspect data
    +head(oa_shp[1:10, c("msoa_cd", "oa_cd", "unemp", "pr_male")])
    Simple feature collection with 6 features and 4 fields
     Geometry type: POLYGON
    @@ -1072,19 +1235,15 @@ 

    We can now estimate our model:

    -
    detach(msoa_shp)
    -attach(oa_shp)
    -
    -
    The following object is masked from package:viridis:
    -
    -    unemp
    -
    -
    # specify a model equation
    -eq5 <- unemp ~ lt_ill + pr_male + (1 | msoa_cd)
    -model5 <- lmer(eq5, data = oa_shp)
    -
    -# estimates
    -summary(model5)
    +
    detach(msoa_shp)
    +attach(oa_shp)
    +
    +# specify a model equation
    +eq5 <- unemp ~ lt_ill + pr_male + (1 | msoa_cd)
    +model5 <- lmer(eq5, data = oa_shp)
    +
    +# estimates
    +summary(model5)
    Linear mixed model fit by REML ['lmerMod']
     Formula: unemp ~ lt_ill + pr_male + (1 | msoa_cd)
    @@ -1114,19 +1273,19 @@ 

    -

    This model includes the proportion of males and intercepts that vary by MSOA. The lmer() function only accepts predictors at the individual level, so we have included data on the proportion of male population at this level. Explore and interpret the model running the functions below:

    +

    This model includes the proportion of males and intercepts that vary by MSOA. The lmer() function only accepts predictors at the individual level, so we have included data on the proportion of male population at this level. Explore and interpret the model running the functions below:

    -
    # fixed effects
    -fixef(model5)
    +
    # fixed effects
    +fixef(model5)
    (Intercept)      lt_ill     pr_male 
      -0.0774607   0.2978084   0.2505913 
    -
    # random effects
    -ranef_m5 <- ranef(model5)
    -head(ranef_m5$msoa_cd, 5)
    +
    # random effects
    +ranef_m5 <- ranef(model5)
    +head(ranef_m5$msoa_cd, 5)
               (Intercept)
     E02001347 -0.013625261
    @@ -1137,16 +1296,14 @@ 

    Adding group-level predictors tends to improve inferences for group coefficients. Examine the confidence intervals, in order to evalute how the precision of our estimates of the MSOA intercepts have changed. Have confidence intervals for the intercepts of Model 4 and 5 increased or reduced? Hint: look at how to get the confidence intervals above.

    -

    - -
    -

    7.5 Questions

    +

    +7.5 Questions

    For the second assignment, we will be using a different dataset comprising information on COVID-19 cases, census data and the Index of Multiple Deprivation (IMD) for England. The data set is similar in structured to that used in this chapter. It is hierarchically organised into 149 Upper Tier Local Authority Districts (UTLADs) within 9 Regions and has 508 variables - see Chapter @ref(datasets) for a more detailed description of the data.

    -
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    +
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    Reading layer `covid19_eng' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' 
       using driver `GPKG'
     Simple feature collection with 149 features and 507 fields
     Geometry type: MULTIPOLYGON
    @@ -1157,7 +1314,7 @@ 

    -
    head(sdf[1:5,c(3,4,9,10,381,385,386,387,403,406)])
    +
    head(sdf[1:5,c(3,4,9,10,381,385,386,387,403,406)])
    Simple feature collection with 5 features and 10 fields
     Geometry type: MULTIPOLYGON
    @@ -1185,16 +1342,26 @@ 

    File 11: upper-tier local authority summaries for information on this and associated indicators. -
  • Residents: Number of residents
  • -
  • Households: Number of households
  • -
  • Dwellings: Number of dwellings
  • -
  • Age_85plus: Number of people aged 85 and over
  • -
  • White_British_and_Irish: Number of white British and Irish people
  • +
  • +ctyua19nm: Upper Tier Local Authority District name
  • +
  • +Region: Region name
  • +
  • +X2020.01.31: COVID-19 cases on January 31st 2020
  • +
  • +X2020.02.01: COVID-19 cases on February 1st 2020
  • +
  • +IMD...Average.score: Average IMD score for UTLADs - see File 11: upper-tier local authority summaries for information on this and associated indicators.
  • +
  • +Residents: Number of residents
  • +
  • +Households: Number of households
  • +
  • +Dwellings: Number of dwellings
  • +
  • +Age_85plus: Number of people aged 85 and over
  • +
  • +White_British_and_Irish: Number of white British and Irish people
  • Note that variable names relating to the daily COVID-19 cases are organised in the following way: X stands for daily COVID-19 cases, followed by the year (i.e. 2020, 2021); month (i.e. January to December); and day (i.e. 01 to 31).

    Using these data, you are required to address the following challenges:

    @@ -1208,7 +1375,8 @@

    -
  • Dependent Variable: We will seek to explain daily COVID-19 cases, and you will need to make a decision as to:
  • +
  • +Dependent Variable: We will seek to explain daily COVID-19 cases, and you will need to make a decision as to:
    • Daily vs cumulative COVID-19 cases. Given that we will be focusing on cross-sectional models (i.e. models for one snapshot), you can focus on modelling daily cases at one specific date or cumulative daily cases over a period of time.

    • @@ -1217,19 +1385,20 @@

      -
      # computing cumulative COVID cases for  01/12/2020 - 29/01/2021
      -sdf[, 509] <- sdf %>% dplyr::select("X2020.12.01":"X2021.01.29") %>% # select COVID cases 01/12/2020 - 29/01/2021
      -  mutate(cum_covid = rowSums(across(where(is.numeric)))) %>% # sum daily cases
      -  dplyr::select(cum_covid) %>% # select cumulative cases
      -   st_set_geometry(., NULL) # set geometry to NULL
      -
      -# computing risk of infection
      -sdf <- sdf %>%  mutate(
      -  covid19_r = round((cum_covid / Residents ) * 1000) 
      -  )
      +
      # computing cumulative COVID cases for  01/12/2020 - 29/01/2021
      +sdf[, 509] <- sdf %>% dplyr::select("X2020.12.01":"X2021.01.29") %>% # select COVID cases 01/12/2020 - 29/01/2021
      +  mutate(cum_covid = rowSums(across(where(is.numeric)))) %>% # sum daily cases
      +  dplyr::select(cum_covid) %>% # select cumulative cases
      +   st_set_geometry(., NULL) # set geometry to NULL
      +
      +# computing risk of infection
      +sdf <- sdf %>%  mutate(
      +  covid19_r = round((cum_covid / Residents ) * 1000) 
      +  )

      -
    1. Explanatory variables:
    2. +
    3. +Explanatory variables:
    • At least 3. Use at least 3 explanatory variables. There is no maximum limit but consider your model to be parsimonious.

    • @@ -1238,45 +1407,37 @@

    ADVICE: Create a new spatial data frame including only the variables you will analyse. For example:

    -
    nsdf <- sdf  %>%  dplyr::select(objectid, 
    -                         ctyua19cd, 
    -                         ctyua19nm, 
    -                         Region, 
    -                         covid19_r, 
    -                         crowded_hou, 
    -                         elderly, 
    -                         ethnic, 
    -                         Residents)
    +
    nsdf <- sdf  %>%  dplyr::select(objectid, 
    +                         ctyua19cd, 
    +                         ctyua19nm, 
    +                         Region, 
    +                         covid19_r, 
    +                         crowded_hou, 
    +                         elderly, 
    +                         ethnic, 
    +                         Residents)
    -
    -
    -
    -
      -
    1. Read the file in R by executing read_tsv("data/mlm/readme.txt") . Ensure the library readr is installed before running read_tsv.↩︎

    2. -
    3. The VPC is equal to the intra-class correlation coefficient which is the correlation between the observations of the dependent variable selected randomly from the same group. For instance, if the VPC is 0.1, we would say that 10% of the variation is between groups and 90% within. The correlation between randomly chosen pairs of observations belonging to the same group is 0.1.↩︎

    4. -
    -
    - -
    - -
    + \ No newline at end of file diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-20-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-20-1.png deleted file mode 100644 index c5160d5..0000000 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-20-1.png and /dev/null differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-21-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-21-1.png new file mode 100644 index 0000000..c7cd20e Binary files /dev/null and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-21-1.png differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-23-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-23-1.png deleted file mode 100644 index 73e32c3..0000000 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-23-1.png and /dev/null differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-24-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-24-1.png new file mode 100644 index 0000000..0a6ba16 Binary files /dev/null and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-24-1.png differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-1.png deleted file mode 100644 index ba24b9b..0000000 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-1.png and /dev/null differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-2.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-2.png deleted file mode 100644 index 67c9069..0000000 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-4-2.png and /dev/null differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-1.png index d6aa2fa..bba0c65 100644 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-1.png and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-1.png differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-2.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-2.png new file mode 100644 index 0000000..1791422 Binary files /dev/null and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-5-2.png differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-6-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-6-1.png new file mode 100644 index 0000000..6065133 Binary files /dev/null and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-6-1.png differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-7-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-7-1.png deleted file mode 100644 index 8f528b0..0000000 Binary files a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-7-1.png and /dev/null differ diff --git a/docs/07-multilevel-01_files/figure-html/unnamed-chunk-8-1.png b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-8-1.png new file mode 100644 index 0000000..1fa59bd Binary files /dev/null and b/docs/07-multilevel-01_files/figure-html/unnamed-chunk-8-1.png differ diff --git a/docs/08-multilevel-02.html b/docs/08-multilevel-02.html index 5eec175..08a180f 100644 --- a/docs/08-multilevel-02.html +++ b/docs/08-multilevel-02.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 8  Multilevel Modelling - Part 2 - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    8  Multilevel Modelling - Part 2

    +
    +

    8  Multilevel Modelling - Part 2

    @@ -303,69 +312,60 @@

    Gelman and Hill (2006) provides an excellent and intuitive explanation of multilevel modelling and data analysis in general. Read Part 2A for a really good explanation of multilevel models.

  • Multilevel Modelling (n.d.) is an useful online resource on multilevel modelling and is free!

  • -
    -

    8.1 Dependencies

    -

    This chapter uses the following libraries which are listed in the Section 1.4.1 in Chapter 1:

    +

    +8.1 Dependencies

    +

    This chapter uses the following libraries which are listed in the Section 1.4.1 in Chapter 1:

    -
    # Data manipulation, transformation and visualisation
    -library(tidyverse)
    -# Nice tables
    -library(kableExtra)
    -# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    -library(sf) 
    -# Spatial objects conversion
    -library(sp) 
    -# Thematic maps
    -library(tmap) 
    -# Colour palettes
    -library(RColorBrewer) 
    -# More colour palettes
    -library(viridis) # nice colour schemes
    -# Fitting multilevel models
    -library(lme4)
    -# Tools for extracting information generated by lme4
    -library(merTools)
    -# Exportable regression tables
    -library(jtools)
    -library(stargazer)
    -library(sjPlot)
    -
    -
    -
    -

    8.2 Data

    +
    # Data manipulation, transformation and visualisation
    +library(tidyverse)
    +# Nice tables
    +library(kableExtra)
    +# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    +library(sf) 
    +# Spatial objects conversion
    +library(sp) 
    +# Thematic maps
    +library(tmap) 
    +# Colour palettes
    +library(viridis) 
    +# Fitting multilevel models
    +library(lme4)
    +# Tools for extracting information generated by lme4
    +library(merTools)
    +# Exportable regression tables
    +library(jtools)
    +

    +

    +8.2 Data

    For this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1

    -

    Let us read the data:

    +

    1 Read the file in R by executing read_tsv("data/mlm/readme.txt") . Ensure the library readr is installed before running read_tsv.

    Let us read the data:

    -
    # clean workspace
    -rm(list=ls())
    -# read data
    -oa_shp <- st_read("data/mlm/OA.shp")
    -
    -
    -
    -

    8.3 Conceptual Overview

    +
    # clean workspace
    +rm(list=ls())
    +# read data
    +oa_shp <- st_read("data/mlm/OA.shp")
    +
    +

    +8.3 Conceptual Overview

    So far, we have estimated varying-intercept models; that is, when the intercept (\(\beta_{0}\)) is allowed to vary by group (eg. geographical area) - as shown in Fig. 1(a). The strength of the relationship between \(y\) (i.e. unemployment rate) and \(x\) (long-term illness) has been assumed to be the same across groups (i.e. MSOAs), as captured by the regression slope (\(\beta_{1}\)). Yet it can also vary by group as shown in Fig. 1(b), or we can observe group variability for both intercepts and slopes as represented in Fig. 1(c).

    -
    -

    -

    Fig. 1. Linear regression model with (a) varying intercepts, (b) varying slopes, and (c) both. Source: Gelman and Hill (2006) p.238.

    -
    +

    +
    Fig. 1. Linear regression model with (a) varying intercepts, (b) varying slopes, and (c) both. Source: Gelman and Hill (2006) p.238.
    -
    -

    8.3.1 Exploratory Analysis: Varying Slopes

    +

    +8.3.1 Exploratory Analysis: Varying Slopes

    Let’s then explore if there is variation in the relationship between unemployment rate and the share of population in long-term illness. We do this by selecting the 8 MSOAs containing OAs with the highest unemployment rates in Liverpool.

    -
    # Sort data 
    -oa_shp <- oa_shp %>% arrange(-unemp)
    -oa_shp[1:9, c("msoa_cd", "unemp")]
    +
    # Sort data 
    +oa_shp <- oa_shp %>% arrange(-unemp)
    +oa_shp[1:9, c("msoa_cd", "unemp")]
    Simple feature collection with 9 features and 2 fields
     Geometry type: MULTIPOLYGON
    @@ -383,40 +383,41 @@ 

    # Select MSOAs
    -s_t8 <- oa_shp %>% dplyr::filter(
    -    as.character(msoa_cd) %in% c(
    -      "E02001354", 
    -      "E02001369", 
    -      "E02001366", 
    -      "E02001365", 
    -      "E02001370", 
    -      "E02001390", 
    -      "E02001368", 
    -      "E02001385")
    -    )

    +
    # Select MSOAs
    +s_t8 <- oa_shp %>% dplyr::filter(
    +    as.character(msoa_cd) %in% c(
    +      "E02001354", 
    +      "E02001369", 
    +      "E02001366", 
    +      "E02001365", 
    +      "E02001370", 
    +      "E02001390", 
    +      "E02001368", 
    +      "E02001385")
    +    )

    And then we generate a set of scatter plots and draw regression lines for each MSOA.

    -
    ggplot(s_t8, aes(x = lt_ill, y = unemp)) + 
    -  geom_point() + 
    -  geom_smooth(method = "lm") +
    -  facet_wrap(~ msoa_cd, nrow = 2) +
    -  ylab("Unemployment rate") + 
    -  xlab("Long-term Illness (%)") +
    -  theme_classic()
    +
    ggplot(s_t8, aes(x = lt_ill, y = unemp)) + 
    +  geom_point() + 
    +  geom_smooth(method = "lm") +
    +  facet_wrap(~ msoa_cd, nrow = 2) +
    +  ylab("Unemployment rate") + 
    +  xlab("Long-term Illness (%)") +
    +  theme_classic()
    `geom_smooth()` using formula = 'y ~ x'
    -

    +
    +

    +
    +

    We can observe great variability in the relationship between unemployment rates and the percentage of population in long-term illness. A strong and positive relationship exists in MSOA E02001366 (Tuebrook and Stoneycroft), while it is negative in MSOA E02001370 (Everton) and neutral in MSOA E02001390 (Princes Park & Riverside). This visual inspection suggests that accounting for differences in the way unmployment rates relate to long-term illness is important. Contextual factors may differ across MSOAs in systematic ways.

    -
    -
    -
    -

    8.4 Estimating Varying Intercept and Slopes Models

    +

    +8.4 Estimating Varying Intercept and Slopes Models

    A way to capture for these group differences in the relationship between unemployment rates and long-term illness is to allow the relevant slope to vary by group (i.e. MSOA). We can do this estimating the following model:

    OA-level:

    \[y_{ij} = \beta_{0j} + \beta_{1j}x_{ij} + e_{ij}\]

    @@ -425,18 +426,18 @@

    \[y_{ij} = (\beta_{0} + u_{0j}) + (\beta_{1} + u_{1j})x_{ij} + e_{ij}\] where, as in the previous Chapter, \(y\) the proportion of unemployed population in OA \(i\) within MSOA \(j\); \(\beta_{0}\) is the fixed intercept (averaging over all MSOAs); \(u_{0j}\) represents the MSOA-level residuals, or random effects, of the intercept; \(e_{ij}\) is the individual-level residuals; and, \(x_{ij}\) represents the percentage of long-term illness population. But now we have a varying slope represented by \(\beta_{1}\) and \(u_{1j}\): \(\beta_{1}\) is estimated average slope - fixed part of the model; and, \(u_{1j}\) is the estimated group-level errors of the slope.

    To estimate such model, we add lt_ill in the bracket with a + sign between 1 and | i.e. (1 + lt_ill | msoa_cd).

    -
    # attach df
    -attach(oa_shp)
    -
    -# change to proportion
    -oa_shp$lt_ill <- lt_ill/100
    -
    -# specify a model equation
    -eq6 <- unemp ~ lt_ill + (1 + lt_ill | msoa_cd)
    -model6 <- lmer(eq6, data = oa_shp)
    -
    -# estimates
    -summary(model6)
    +
    # attach df
    +attach(oa_shp)
    +
    +# change to proportion
    +oa_shp$lt_ill <- lt_ill/100
    +
    +# specify a model equation
    +eq6 <- unemp ~ lt_ill + (1 + lt_ill | msoa_cd)
    +model6 <- lmer(eq6, data = oa_shp)
    +
    +# estimates
    +summary(model6)
    Linear mixed model fit by REML ['lmerMod']
     Formula: unemp ~ lt_ill + (1 + lt_ill | msoa_cd)
    @@ -469,7 +470,7 @@ 

    -
    fixef(model6)
    +
    fixef(model6)
    (Intercept)      lt_ill 
      0.04765009  0.30125875 
    @@ -478,8 +479,8 @@

    \(y = 0.04764998 + 0.30125916x\). The fixed intercept indicates that the average unemployment rate is 0.05 if the percentage of population with long-term illness is zero.The fixed slope indicates that the average relationship between unemployment rate and long-term illness is positive across MSOAs i.e. as the percentage of population with long-term illness increases by 1 percentage point, the unemployment rate increases by 0.3.

    We look the estimated MSOA-level errors (random effects):

    -
    ranef_m6 <- ranef(model6)
    -head(ranef_m6$msoa_cd, 5)
    +
    ranef_m6 <- ranef(model6)
    +head(ranef_m6$msoa_cd, 5)
               (Intercept)      lt_ill
     E02001347 -0.026561345  0.02718102
    @@ -492,24 +493,27 @@ 

    -
    #coef(model6)
    +
    #coef(model6)

    We are normally more interested in identifying the extent of deviation and its significance. To this end, we create a caterpillar plot:

    -
    # plot
    -plotREsim(REsim(model6))
    +
    # plot
    +plotREsim(REsim(model6))
    -

    +
    +

    +
    +

    These plots reveal some interesting patterns. First, only one MSOA, containing wards such as Tuebrook and Stoneycroft, Anfield & Everton, seems to have a statistically significantly different intercept, or average unemployment rate. Confidence intervals overlap zero for all other 60 MSOAs. Despite this, note that when a slope is allowed to vary by group, it generally makes sense for the intercept to also vary. Second, significant variability exists in the association between unemployment rate and long-term illness across MSOAs. Ten MSOAs display a significant positive association, while 12 exhibit a significantly negative relationship. Third, these results reveal that geographical differences in the relationship between unemployment rate and long-term illness can explain the significant differences in average unemployment rates in the varying intercept only model.

    Let’s try to get a better understanding of the varying relationship between unemployment rate and long-term illness by mapping the relevant MSOA-level errors.

    -
    # read data
    -msoa_shp <- st_read("data/mlm/MSOA.shp")
    +
    # read data
    +msoa_shp <- st_read("data/mlm/MSOA.shp")
    Reading layer `MSOA' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/mlm/MSOA.shp' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/MSOA.shp' 
       using driver `ESRI Shapefile'
     Simple feature collection with 61 features and 17 fields
     Geometry type: MULTIPOLYGON
    @@ -517,42 +521,45 @@ 

    # create a dataframe for MSOA-level random effects
    -re_msoa_m6 <- REsim(model6) %>% filter(groupFctr == "msoa_cd") %>%
    -  filter(term == "lt_ill")
    -str(re_msoa_m6)

    +
    # create a dataframe for MSOA-level random effects
    +re_msoa_m6 <- REsim(model6) %>% filter(groupFctr == "msoa_cd") %>%
    +  filter(term == "lt_ill")
    +str(re_msoa_m6)
    'data.frame':   61 obs. of  6 variables:
      $ groupFctr: chr  "msoa_cd" "msoa_cd" "msoa_cd" "msoa_cd" ...
      $ groupID  : chr  "E02001347" "E02001348" "E02001349" "E02001350" ...
      $ term     : chr  "lt_ill" "lt_ill" "lt_ill" "lt_ill" ...
    - $ mean     : num  0.0338 -0.1135 0.0579 -0.1473 -0.2838 ...
    - $ median   : num  0.0328 -0.1167 0.0591 -0.1486 -0.2833 ...
    - $ sd       : num  0.0463 0.0686 0.0789 0.0388 0.0415 ...
    + $ mean : num 0.026 -0.1126 0.0516 -0.1393 -0.2795 ... + $ median : num 0.026 -0.1147 0.0587 -0.1422 -0.2815 ... + $ sd : num 0.0427 0.0746 0.0826 0.0356 0.0401 ...

    -
    # merge data
    -msoa_shp <- merge(x = msoa_shp, y = re_msoa_m6, by.x = "MSOA_CD", by.y = "groupID")
    +
    # merge data
    +msoa_shp <- merge(x = msoa_shp, y = re_msoa_m6, by.x = "MSOA_CD", by.y = "groupID")
    -
    # ensure geometry is valid
    -msoa_shp = sf::st_make_valid(msoa_shp)
    -
    -# create a map
    -legend_title = expression("MSOA-level residuals")
    -map_msoa = tm_shape(msoa_shp) +
    -  tm_fill(col = "median", title = legend_title, palette = magma(256, begin = 0, end = 1), style = "cont") + 
    -  tm_borders(col = "white", lwd = .01)  + 
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    -map_msoa
    +
    # ensure geometry is valid
    +msoa_shp = sf::st_make_valid(msoa_shp)
    +
    +# create a map
    +legend_title = expression("MSOA-level residuals")
    +map_msoa = tm_shape(msoa_shp) +
    +  tm_fill(col = "median", title = legend_title, palette = magma(256, begin = 0, end = 1), style = "cont") + 
    +  tm_borders(col = "white", lwd = .01)  + 
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 4) + 
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position =  c("center", "bottom")) 
    +map_msoa
    -

    +
    +

    +
    +

    The map indicates that the relationship between unemployment rate and long-term illness is tends to stronger and positive in northern MSOAs; that is, the percentage of population with long-term illness explains a greater share of the variation in unemployment rates in these locations. As expected, a greater share of population in long-term illness is associated with higher local unemployment. In contrast, the relationship between unemployment rate and long-term illness tends to operate in the reverse direction in north-east and middle-southern MSOAs. In these MSOAs, OAs tend to have a higher unemployment rate relative the share of population in long-term illness. You can confirm this examining the data for specific MSOA executing:

    -
    oa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp, lt_ill) %>%
    -    filter(as.character(msoa_cd) == "E02001370")
    +
    oa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp, lt_ill) %>%
    +    filter(as.character(msoa_cd) == "E02001370")
    Simple feature collection with 23 features and 4 fields
     Geometry type: MULTIPOLYGON
    @@ -584,23 +591,22 @@ 

    Section 7.4.5 and Section 7.4.6 in Chapter 7.

    -

    -
    -

    8.5 Interpreting Correlations Between Group-level Intercepts and Slopes

    +

    Now try adding a group-level predictor and an individual-level predictor to the model. Unsure, look at Section 7.4.5 and Section 7.4.6 in Chapter 7.

    +

    +8.5 Interpreting Correlations Between Group-level Intercepts and Slopes

    Correlations of random effects are confusing to interpret. Key for their appropriate interpretation is to recall they refer to group-level residuals i.e. deviation of intercepts and slopes from the average model intercept and slope. A strong negative correlation indicates that groups with high intercepts have relatively low slopes, and vice versa. A strong positive correlation indicates that groups with high intercepts have relatively high slopes, and vice versa. A correlation close to zero indicate little or no systematic between intercepts and slopes. Note that a high correlation between intercepts and slopes is not a problem, but it makes the interpretation of the estimated intercepts more challenging. For this reason, a suggestion is to center predictors (\(x's\)); that is, substract their average value (\(z = x - \bar{x}\)). For a more detailed discussion, see Multilevel Modelling (n.d.).

    To illustrate this, let’s reestimate our model adding an individual-level predictor: the share of population with no educational qualification.

    -
    # centering to the mean
    -oa_shp$z_no_qual <- no_qual/100 - mean(no_qual/100)
    -oa_shp$z_lt_ill <- lt_ill - mean(lt_ill)
    -
    -# specify a model equation
    -eq7 <- unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)
    -model7 <- lmer(eq7, data = oa_shp)
    -
    -# estimates
    -summary(model7)
    +
    # centering to the mean
    +oa_shp$z_no_qual <- no_qual/100 - mean(no_qual/100)
    +oa_shp$z_lt_ill <- lt_ill - mean(lt_ill)
    +
    +# specify a model equation
    +eq7 <- unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)
    +model7 <- lmer(eq7, data = oa_shp)
    +
    +# estimates
    +summary(model7)
    Linear mixed model fit by REML ['lmerMod']
     Formula: unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)
    @@ -632,9 +638,8 @@ 

    -

    8.6 Model building

    +

    +8.6 Model building

    Now we know how to estimate multilevel regression models in R. The question that remains is: When does multilevel modeling make a difference? The short answer is: when there is little group-level variation. When there is very little group-level variation, the multilevel modelling reduces to classical linear regression estimates with no group indicators. Inversely, when group-level coefficients vary greatly (compared to their standard errors of estimation), multilevel modelling reduces to classical regression with group indicators Gelman and Hill (2006).

    How do you go about building a model?

    We generally start simple by fitting simple linear regressions and then work our way up to a full multilevel model - see Gelman and Hill (2006) p. 270.

    @@ -642,11 +647,11 @@

    As an absolute minimum, more than two groups are required. With only one or two groups, a multilevel model reduces to a linear regression model.

    How many observations per group?

    Two observations per group is sufficient to fit a multilevel model.

    -
    -

    8.6.1 Model Comparison

    -

    How we assess different candidate models? We can use the function anova() and assess various statistics: The Akaike Information Criterion (AIC), the Bayesian Information Criterion (BIC), Loglik and Deviance. Generally, we look for lower scores for all these indicators. We can also refer to the Chisq statistic below. It tests the hypothesis of whether additional predictors improve model fit. Particularly it tests the Null Hypothesis whether the coefficients of the additional predictors equal 0. It does so comparing the deviance statistic and determining if changes in the deviance are statistically significant. Note that a major limitation of the deviance test is that it is for nested models i.e. a model being compared must be nested in the other. Below we compare our two models. The results indicate that adding an individual-level predictor (i.e. the share of population with no qualification) provides a model with better.

    +

    +8.6.1 Model Comparison

    +

    How we assess different candidate models? We can use the function anova() and assess various statistics: The Akaike Information Criterion (AIC), the Bayesian Information Criterion (BIC), Loglik and Deviance. Generally, we look for lower scores for all these indicators. We can also refer to the Chisq statistic below. It tests the hypothesis of whether additional predictors improve model fit. Particularly it tests the Null Hypothesis whether the coefficients of the additional predictors equal 0. It does so comparing the deviance statistic and determining if changes in the deviance are statistically significant. Note that a major limitation of the deviance test is that it is for nested models i.e. a model being compared must be nested in the other. Below we compare our two models. The results indicate that adding an individual-level predictor (i.e. the share of population with no qualification) provides a model with better.

    -
    anova(model6, model7)
    +
    anova(model6, model7)
    refitting model(s) with ML (instead of REML)
    @@ -662,16 +667,14 @@

    - - -
    -

    8.7 Questions

    -

    We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

    +

    +8.7 Questions

    +

    We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

    -
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    +
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    Reading layer `covid19_eng' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' 
       using driver `GPKG'
     Simple feature collection with 149 features and 507 fields
     Geometry type: MULTIPOLYGON
    @@ -686,7 +689,7 @@ 

    Chapter 7 challenge, so you can compare the model results from this chapter.

    +

    Use the same explanatory variables used for the Chapter 7 challenge, so you can compare the model results from this chapter.

    Analyse and discuss:

    1. the varying slope estimate(s) from your model(s) (to what extent does the relationship between your dependent and independent variables vary across groups / areas? are they statistically significantly different?).
    2. @@ -694,24 +697,17 @@

    -
    -
    -
      -
    1. Read the file in R by executing read_tsv("data/mlm/readme.txt") . Ensure the library readr is installed before running read_tsv.↩︎

    2. -
    -
    - -
    - -
    + \ No newline at end of file diff --git a/docs/08-multilevel-02_files/figure-html/unnamed-chunk-11-1.png b/docs/08-multilevel-02_files/figure-html/unnamed-chunk-11-1.png index 6610677..dccae7f 100644 Binary files a/docs/08-multilevel-02_files/figure-html/unnamed-chunk-11-1.png and b/docs/08-multilevel-02_files/figure-html/unnamed-chunk-11-1.png differ diff --git a/docs/08-multilevel-02_files/figure-html/unnamed-chunk-9-1.png b/docs/08-multilevel-02_files/figure-html/unnamed-chunk-9-1.png index 7f634a0..3de9ae6 100644 Binary files a/docs/08-multilevel-02_files/figure-html/unnamed-chunk-9-1.png and b/docs/08-multilevel-02_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/docs/09-gwr.html b/docs/09-gwr.html index 8da6208..de5496c 100644 --- a/docs/09-gwr.html +++ b/docs/09-gwr.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 9  Geographically Weighted Regression - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    9  Geographically Weighted Regression

    +
    +

    9  Geographically Weighted Regression

    @@ -310,46 +319,42 @@

    Fotheringham, Brunsdon, and Charlton (2002), a must-go book if you are working or planning to start working on geographically weighted regression modelling.

  • Comber et al. (2022) provide a roadmap to approach various practical issues in the application of GWR.

  • -
    -

    9.1 Dependencies

    +

    +9.1 Dependencies

    This chapter uses the following libraries:

    -
    # Data manipulation, transformation and visualisation
    -library(tidyverse)
    -# Nice tables
    -library(kableExtra)
    -# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    -library(sf) 
    -# Spatial objects conversion
    -library(sp) 
    -# Thematic maps
    -library(tmap) 
    -# Colour palettes
    -library(RColorBrewer) 
    -# More colour palettes
    -library(viridis) # nice colour schemes
    -# Fitting geographically weighted regression models
    -library(spgwr)
    -# Obtain correlation coefficients
    -library(corrplot)
    -# Exportable regression tables
    -library(jtools)
    -library(stargazer)
    -library(sjPlot)
    -# Assess multicollinearity
    -library(car)
    +
    # Data manipulation, transformation and visualisation
    +library(tidyverse)
    +# Nice tables
    +library(kableExtra)
    +# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    +library(sf) 
    +# Spatial objects conversion
    +library(sp) 
    +# Thematic maps
    +library(tmap) 
    +# Colour palettes
    +library(RColorBrewer) 
    +# More colour palettes
    +library(viridis) # nice colour schemes
    +# Fitting geographically weighted regression models
    +library(spgwr)
    +# Obtain correlation coefficients
    +library(corrplot)
    +# Exportable regression tables
    +library(jtools)
    +# Assess multicollinearity
    +library(car)
    -
    -
    -

    9.2 Data

    +

    +9.2 Data

    For this chapter, we will use data on:

    • cumulative COVID-19 confirmed cases from 1st January, 2020 to 14th April, 2020 from Public Health England via the GOV.UK dashboard;

    • @@ -358,22 +363,21 @@

      Counties and Unitary Authorities. They are the geographical units used to report COVID-19 data.

      If you use the dataset utilised in this chapter, make sure cite this book. For a full list of the variables included in the data set used in this Chapter, see the readme file in the gwr data folder.1

      -

      Let’s read the data:

      +

      1 Read the file in R by executing read_tsv("data/gwr/readme.txt"). Ensure the library readr is installed before running read_tsv.99079

      Let’s read the data:

      -
      # clean workspace
      -rm(list=ls())
      -# read data
      -utla_shp <- st_read("data/gwr/Covid19_total_cases_geo.shp") %>%
      -  select(objct, cty19c, ctyu19nm, long, lat, st_rs, st_ln, X2020.04.14, I.PL1, IMD20, IMD2., Rsdnt, Hshld, Dwlln, Hsh_S, E_16_, A_65_, Ag_85, Mixed, Indin, Pkstn, Bngld, Chins, Oth_A, Black, Othr_t, CB_U_, Crwd_, Lng__, Trn__, Adm__, Ac___, Pb___, Edctn, H____, geometry)
      -
      -# replace nas with 0s
      -utla_shp[is.na(utla_shp)] <- 0
      -# explore data
      -str(utla_shp)
      +
      # clean workspace
      +rm(list=ls())
      +# read data
      +utla_shp <- st_read("data/gwr/Covid19_total_cases_geo.shp") %>%
      +  select(objct, cty19c, ctyu19nm, long, lat, st_rs, st_ln, X2020.04.14, I.PL1, IMD20, IMD2., Rsdnt, Hshld, Dwlln, Hsh_S, E_16_, A_65_, Ag_85, Mixed, Indin, Pkstn, Bngld, Chins, Oth_A, Black, Othr_t, CB_U_, Crwd_, Lng__, Trn__, Adm__, Ac___, Pb___, Edctn, H____, geometry)
      +
      +# replace nas with 0s
      +utla_shp[is.na(utla_shp)] <- 0
      +# explore data
      +str(utla_shp)
      -

    -
    -

    9.3 Recap: Spatial Effects

    +

    +9.3 Recap: Spatial Effects

    To this point, we have implicitly discussed three distinctive spatial effects:

    • Spatial heterogeneity refers to the uneven distribution of a variable’s values across space

    • @@ -381,24 +385,26 @@

      Spatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space

    In previous sessions, we considered multilevel models to deal with spatial nonstationarity, recognising that the strength and direction of the relationship between an outcome \(y\) and a set of predictors \(x\) may vary over space. Here we consider a different approach, namely geographically weighted regression (GWR).

    -
    -
    -

    9.4 Exploratory Analysis

    +

    +9.4 Exploratory Analysis

    We will explore this technique through an empirical analysis considering the current global COVID-19 outbreak. Specifically we will seek to identify potential contextual factors that may be related to an increased risk of local infection. Population density, overcrowded housing, vulnerable individuals and critical workers have all been linked to a higher risk of COVID-19 infection.

    First, we will define and develop some basic understanding of our variable of interest. We define the risk of COVID-19 infection by the cumulative number of confirmed positive cases COVID-19 per 100,000 people:

    -
    # risk of covid-19 infection
    -utla_shp$covid19_r <- (utla_shp$X2020.04.14 / utla_shp$Rsdnt) * 100000
    -
    -# histogram
    -ggplot(data = utla_shp) +
    -geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = covid19_r)) +
    -   theme_classic()
    +
    # risk of covid-19 infection
    +utla_shp$covid19_r <- (utla_shp$X2020.04.14 / utla_shp$Rsdnt) * 100000
    +
    +# histogram
    +ggplot(data = utla_shp) +
    +geom_density(alpha=0.8, colour="black", fill="lightblue", aes(x = covid19_r)) +
    +   theme_classic()
    -

    +
    +

    +
    +
    -
    # distribution in numbers
    -summary(utla_shp$covid19_r)
    +
    # distribution in numbers
    +summary(utla_shp$covid19_r)
       Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
       32.11   92.81  140.15  146.66  190.96  341.56 
    @@ -406,11 +412,11 @@

    The results indicate a wide variation in the risk of infection across UTLAs in England, ranging from 31 to 342 confirmed positive cases of COVID-19 per 100,000 people with a median of 147. We map the cases to understand their spatial structure.

    -
    # read region boundaries for a better looking map
    -reg_shp <- st_read("data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp")
    +
    # read region boundaries for a better looking map
    +reg_shp <- st_read("data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp")
    Reading layer `Regions_December_2019_Boundaries_EN_BGC' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp' 
       using driver `ESRI Shapefile'
     Simple feature collection with 9 features and 9 fields
     Geometry type: MULTIPOLYGON
    @@ -418,29 +424,32 @@ 

    -
    # ensure geometry is valid
    -utla_shp = sf::st_make_valid(utla_shp)
    -reg_shp = sf::st_make_valid(reg_shp)
    -
    -# map
    -legend_title = expression("Cumulative cases per 100,000")
    -map_utla = tm_shape(utla_shp) +
    -  tm_fill(col = "covid19_r", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_utla + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    +
    # ensure geometry is valid
    +utla_shp = sf::st_make_valid(utla_shp)
    +reg_shp = sf::st_make_valid(reg_shp)
    +
    +# map
    +legend_title = expression("Cumulative cases per 100,000")
    +map_utla = tm_shape(utla_shp) +
    +  tm_fill(col = "covid19_r", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_utla + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    -

    +
    +

    +
    +

    The map shows that concentrations of high incidence of infections in the metropolitan areas of London, Liverpool, Newcastle, Sheffield, Middlesbrough and Birmingham. Below we list the UTLAs in these areas in descending order.

    -
    hotspots <- utla_shp %>% select(ctyu19nm, covid19_r) %>%
    -  filter(covid19_r > 190)
    -hotspots[order(-hotspots$covid19_r),]
    +
    hotspots <- utla_shp %>% select(ctyu19nm, covid19_r) %>%
    +  filter(covid19_r > 190)
    +hotspots[order(-hotspots$covid19_r),]
    Simple feature collection with 38 features and 2 fields
     Geometry type: GEOMETRY
    @@ -464,45 +473,47 @@ 

    Challenge 1: How does Liverpool ranked in this list?

    -

    -
    -

    9.5 Global Regression

    +

    +9.5 Global Regression

    To provide an intuitive understanding of GWR, a useful start is to explore the data using an ordinary least squares (OLS) linear regression model. The key issue here is to understand if high incidence of COVID-19 is linked to structural differences across UTLAs in England. As indicated above, confirmed positive cases of COVID-19 have been associated with overcrowded housing, vulnerable populations - including people in elderly age groups, economically disadvantaged groups and those suffering from chronic health conditions - ethnic minorities, critical workers in the health & social work, education, accommodation & food, transport, and administrative & support sectors. So, let’s create a set of variables to approximate these factors.

    -
    # define predictors
    -utla_shp <- utla_shp %>% mutate(
    -  crowded_hou = Crwd_ / Hshld, # share of crowded housing
    -  elderly = (A_65_ + Ag_85) / Rsdnt, # share of population aged 65+
    -  lt_illness = Lng__ / Rsdnt, # share of population in long-term illness
    -  ethnic = (Mixed + Indin + Pkstn + Bngld + Chins + Oth_A + Black + Othr_t) / Rsdnt, # share of nonwhite population
    -  imd19_ext = IMD20, # proportion of a larger area’s population living in the most deprived LSOAs in the country
    -  hlthsoc_sec = H____ / E_16_, # share of workforce in the human health & social work sector
    -  educ_sec = Edctn / E_16_, # share of workforce in the education sector
    -  trnsp_sec= Trn__ / E_16_, # share of workforce in the Transport & storage sector
    -  accfood_sec = Ac___ / E_16_, # share of workforce in the accommodation & food service sector
    -  admsupport_sec = Adm__ /  E_16_, # share of workforce in the administrative & support sector
    -  pblic_sec = Pb___ / E_16_ # share of workforce in the public administration & defence sector
    -)
    +
    # define predictors
    +utla_shp <- utla_shp %>% mutate(
    +  crowded_hou = Crwd_ / Hshld, # share of crowded housing
    +  elderly = (A_65_ + Ag_85) / Rsdnt, # share of population aged 65+
    +  lt_illness = Lng__ / Rsdnt, # share of population in long-term illness
    +  ethnic = (Mixed + Indin + Pkstn + Bngld + Chins + Oth_A + Black + Othr_t) / Rsdnt, # share of nonwhite population
    +  imd19_ext = IMD20, # proportion of a larger area’s population living in the most deprived LSOAs in the country
    +  hlthsoc_sec = H____ / E_16_, # share of workforce in the human health & social work sector
    +  educ_sec = Edctn / E_16_, # share of workforce in the education sector
    +  trnsp_sec= Trn__ / E_16_, # share of workforce in the Transport & storage sector
    +  accfood_sec = Ac___ / E_16_, # share of workforce in the accommodation & food service sector
    +  admsupport_sec = Adm__ /  E_16_, # share of workforce in the administrative & support sector
    +  pblic_sec = Pb___ / E_16_ # share of workforce in the public administration & defence sector
    +)

    Let’s quickly examine how they correlate to our outcome variable i.e. incidence rate of COVID-19 using correlation coefficients and correlograms.

    -
    # obtain a matrix of Pearson correlation coefficients
    -df_sel <- st_set_geometry(utla_shp[,37:48], NULL) # temporary data set removing geometries
    -cormat <- cor(df_sel, use="complete.obs", method="pearson")
    -
    -# significance test
    -sig1 <- corrplot::cor.mtest(df_sel, conf.level = .95)
    -
    -# creta a correlogram
    -corrplot::corrplot(cormat, type="lower",
    -                   method = "circle", 
    -                   order = "original", 
    -                   tl.cex = 0.7,
    -                   p.mat = sig1$p, sig.level = .05, 
    -                   col = viridis::viridis(100, option = "plasma"),
    -                   diag = FALSE)
    +
    # obtain a matrix of Pearson correlation coefficients
    +df_sel <- st_set_geometry(utla_shp[,37:48], NULL) # temporary data set removing geometries
    +cormat <- cor(df_sel, use="complete.obs", method="pearson")
    +
    +# significance test
    +sig1 <- corrplot::cor.mtest(df_sel, conf.level = .95)
    +
    +# creta a correlogram
    +corrplot::corrplot(cormat, type="lower",
    +                   method = "circle", 
    +                   order = "original", 
    +                   tl.cex = 0.7,
    +                   p.mat = sig1$p, sig.level = .05, 
    +                   col = viridis::viridis(100, option = "plasma"),
    +                   diag = FALSE)
    -

    +
    +

    +
    +

    The correlogram shows the strength and significance of the linear relationship between our set of variables. The size of the circle reflects the strength of the relationships as captured by the Pearson correlation coefficient, and crosses indicate statistically insignificant relationships at the 95% level of confidence. The colour indicate the direction of the relationship with dark (light) colours indicating a negative (positive) association.

    @@ -511,19 +522,19 @@

    Challenge 2: Analyse the relationship of all the variables executing pairs(df_sel). How accurate would a linear regression be in capturing the relationships for our set of variables?

    -
    -

    9.5.1 Global Regression Results

    +

    +9.5.1 Global Regression Results

    To gain a better understanding of these relationships, we can regress the incidence rate of COVID-19 on a series of factors capturing differences across areas. To focus on the description of GWR, we keep our analysis simple and study the incidence rate of COVID-19 as a function of the share of nonwhite ethnic population and of population suffering from long-term illness by estimating the following OLS linear regression model:

    -
    # attach data
    -attach(utla_shp)
    -
    -# specify a model equation
    -eq1 <- covid19_r ~ ethnic + lt_illness
    -model1 <- lm(formula = eq1, data = utla_shp)
    -
    -# estimates
    -summary(model1)
    +
    # attach data
    +attach(utla_shp)
    +
    +# specify a model equation
    +eq1 <- covid19_r ~ ethnic + lt_illness
    +model1 <- lm(formula = eq1, data = utla_shp)
    +
    +# estimates
    +summary(model1)
    
     Call:
    @@ -548,7 +559,7 @@ 

    -
    vif(model1)
    +
    vif(model1)
        ethnic lt_illness 
        1.43015    1.43015 
    @@ -558,90 +569,84 @@

    \(R^{2}\) value for the OLS regression is 0.393 indicating that our model explains only 39% of the variance in the rate of COVID-19 infection. This leaves 71% of the variance unexplained. Some of this unexplained variance can be because we have only included two explanatory variables in our model, but also because the OLS regression model assumes that the relationships in the model are constant over space; that is, it assumes a stationary process. Hence, an OLS regression model is considered to capture global relationships. However, relationships may vary over space. Suppose, for instance, that there are intrinsic behavioural variations across England and that people have adhered more strictly to self-isolation and social distancing measures in some areas than in others, or that ethnic minorities are less exposed to contracting COVID-19 in certain parts of England. If such variations in associations exist over space, our estimated OLS model will be a misspecification of reality because it assumes these relationships to be constant.

    To better understand this potential misspecification, we investigate the model residuals which show high variability (see below). The distribution is non-random displaying large positive residuals in the metropolitan areas of London, Liverpool, Newcastle (in light colours) and the Lake District and large negative residuals across much of England (in black). This conforms to the spatial pattern of confirmed COVID-19 cases with high concentration in a limited number of metropolitan areas (see above). While our residual map reveals that there is a problem with the OLS model, it does not indicate which, if any, of the parameters in the model might exhibit spatial nonstationarity. A simple way of examining if the relationships being modelled in our global OLS model are likely to be stationary over space would be to estimate separate OLS model for each UTLA in England. But this would require higher resolution i.e. data within UTLA, and we only have one data point per UTLA. -Fotheringham, Brunsdon, and Charlton (2002) (2002, p.40-44) discuss alternative approaches and their limitations.

    -
    utla_shp$res_m1 <- residuals(model1)
    -
    -# map
    -legend_title = expression("OLS residuals")
    -map_utla = tm_shape(utla_shp) +
    -  tm_fill(col = "res_m1", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_utla + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    +
    utla_shp$res_m1 <- residuals(model1)
    +
    +# map
    +legend_title = expression("OLS residuals")
    +map_utla = tm_shape(utla_shp) +
    +  tm_fill(col = "res_m1", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_utla + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    -

    +
    +

    +
    +
    -

    -
    -
    -

    9.6 Fitting a Geographically Weighted Regression

    +

    +9.6 Fitting a Geographically Weighted Regression

    GWR overcomes the limitation of the OLS regression model of generating a global set of estimates. The basic idea behind GWR is to examine the way in which the relationships between a dependent variable and a set of predictors might vary over space. GWR operates by moving a search window from one regression point to the next, working sequentially through all the existing regression points in the dataset. A set of regions is then defined around each regression point and within the search window. A regression model is then fitted to all data contained in each of the identified regions around a regression point, with data points closer to the sample point being weighted more heavily than are those farther away. This process is repeated for all samples points in the dataset. For a data set of 150 observations GWR will fit 150 weighted regression models. The resulting local estimates can then be mapped at the locations of the regression points to view possible variations in the relationships between variables.

    Graphically, GWR involves fitting a spatial kernel to the data as described in the Fig. 1. For a given regression point \(X\), the weight (\(W\)) of a data point is at a maximum at the location of the regression point. The weight decreases gradually as the distance between two points increases. A regression model is thus calibrated locally by moving the regression point across the area under study. For each location, the data are weighted differently so that the resulting estimates are unique to a particular location.

    -
    -

    -

    Fig. 1. GWR with fixed spatial kernel. Source: Fotheringham et al. (2002, 45).

    -
    +

    +
    Fig. 1. GWR with fixed spatial kernel. Source: Fotheringham et al. (2002, 45).
    -
    -

    9.6.1 Fixed or Adaptive Kernel

    +

    +9.6.1 Fixed or Adaptive Kernel

    A key issue is to decide between two options of spatial kernels: a fixed kernel or an adaptive kernel. Intuitively, a fixed kernel involves using a fixed bandwidth to define a region around all regression points as displayed in Fig. 1. The extent of the kernel is determined by the distance to a given regression point, with the kernel being identical at any point in space. An adaptive kernel involves using varying bandwidth to define a region around regression points as displayed in Fig. 2. The extent of the kernel is determined by the number of nearest neighbours from a given regression point. The kernels have larger bandwidths where the data are sparse.

    -
    -

    -

    Fig. 2. GWR with adaptive spatial kernel. Source: Fotheringham et al. (2002, 47).

    -
    +

    +
    Fig. 2. GWR with adaptive spatial kernel. Source: Fotheringham et al. (2002, 47).
    -
    -
    -

    9.6.2 Optimal Bandwidth

    +

    +9.6.2 Optimal Bandwidth

    A second issue is to define the extent of geographical area (i.e. optimal bandwidth) of the spatial kernel. The bandwidth is the distance beyond which a value of zero is assigned to weight observations. Larger bandwidths include a larger number of observations receiving a non-zero weight and more observations are used to fit a local regression.

    To determine the optimal bandwidth, a cross-validation approach is applied; that is, for a location, a local regression is fitted based on a given bandwidth and used to predict the value of the dependent variable. The resulting predicted value is used to compute the residuals of the model. Residuals are compared using a series of bandwidth and the bandwidth returning the smallest local residuals are selected.

    Variance and Bias Trade off

    Choosing an optimal bandwidth involves a compromise between bias and precision. For example, a larger bandwidth will involve using a larger number of observations to fit a local regression, and hence result in reduced variance (or increased precision) but high bias of estimates. On the other hand, too small bandwidth involves using a very small number of observations resulting in increased variance but small bias. An optimal bandwidth offers a compromise between bias and variance.

    -
    -
    -

    9.6.3 Shape of Spatial Kernel

    +

    +9.6.3 Shape of Spatial Kernel

    Two general set of kernel functions can be distinguished: continuous kernels and kernels with compact support. Continuous kernels are used to weight all observations in the study area and includes uniform, Gaussian and Exponential kernel functions. Kernel with compact support are used to assign a nonzero weight to observations within a certain distance and a zero weight beyond it. The shape of the kernel has been reported to cause small changes to resulting estimates (Brunsdon, Fotheringham, and Charlton 1998).

    -
    -
    -

    9.6.4 Selecting a Bandwidth

    +

    +9.6.4 Selecting a Bandwidth

    Let’s now implement a GWR model. The first key step is to define the optimal bandwidth. We first illustrate the use of a fixed spatial kernel.

    -
    -

    9.6.4.1 Fixed Bandwidth

    +

    +9.6.4.1 Fixed Bandwidth

    Cross-validation is used to search for the optimal bandwidth. Recall that this procedure compares the model residuals based on different bandwidths and chooses the optimal solution i.e. the bandwidth returning the smallest model residuals based on a given model specification. A key parameter here is the shape of the geographical weight function (gweight). We set it to be a Gaussian function which is the default. A bi-square function is recommended to reduce computational time. Since we have a simple model, a Gaussian function should not take that long. Note that we set the argument longlat to TRUE and use latitude and longitude for coordinates (coords). When longlat is set to TRUE, distances are measured in kilometres.

    -
    # find optimal kernel bandwidth using cross validation
    -fbw <- gwr.sel(eq1, 
    -               data = utla_shp, 
    -               coords=cbind( long, lat),
    -               longlat = TRUE,
    -               adapt=FALSE, 
    -               gweight = gwr.Gauss, 
    -               verbose = FALSE)
    -
    -# view selected bandwidth
    -fbw
    +
    # find optimal kernel bandwidth using cross validation
    +fbw <- gwr.sel(eq1, 
    +               data = utla_shp, 
    +               coords=cbind( long, lat),
    +               longlat = TRUE,
    +               adapt=FALSE, 
    +               gweight = gwr.Gauss, 
    +               verbose = FALSE)
    +
    +# view selected bandwidth
    +fbw
    [1] 29.30417

    The result indicates that the optimal bandwidth is 39.79 kms. This means that neighbouring UTLAs within a fixed radius of 39.79 kms will be taken to estimate local regressions. To estimate a GWR, we execute the code below in which the optimal bandwidth above is used as an input in the argument bandwidth.

    -
    # fit a gwr based on fixed bandwidth
    -fb_gwr <- gwr(eq1, 
    -            data = utla_shp,
    -            coords=cbind( long, lat),
    -            longlat = TRUE,
    -            bandwidth = fbw, 
    -            gweight = gwr.Gauss,
    -            hatmatrix=TRUE, 
    -            se.fit=TRUE)
    -
    -fb_gwr
    +
    # fit a gwr based on fixed bandwidth
    +fb_gwr <- gwr(eq1, 
    +            data = utla_shp,
    +            coords=cbind( long, lat),
    +            longlat = TRUE,
    +            bandwidth = fbw, 
    +            gweight = gwr.Gauss,
    +            hatmatrix=TRUE, 
    +            se.fit=TRUE)
    +
    +fb_gwr
    Call:
     gwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), 
    @@ -670,60 +675,62 @@ 

    We will skip the interpretation of the results for now and consider them in the next section. Now, we want to focus on the overall model fit and will map the results of the \(R^{2}\) for the estimated local regressions. To do this, we extract the model results stored in a Spatial Data Frame (SDF) and add them to our spatial data frame utla_shp. Note that the Quasi-global \(R^{2}\) is very high (0.77) indicating a high in-sample prediction accuracy.

    -
    # write gwr output into a data frame
    -fb_gwr_out <- as.data.frame(fb_gwr$SDF)
    -
    -utla_shp$fmb_localR2 <- fb_gwr_out$localR2
    -
    -# map
    -  # Local R2
    -legend_title = expression("Fixed: Local R2")
    -map_fbgwr1 = tm_shape(utla_shp) +
    -  tm_fill(col = "fmb_localR2", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_fbgwr1 + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    +
    # write gwr output into a data frame
    +fb_gwr_out <- as.data.frame(fb_gwr$SDF)
    +
    +utla_shp$fmb_localR2 <- fb_gwr_out$localR2
    +
    +# map
    +  # Local R2
    +legend_title = expression("Fixed: Local R2")
    +map_fbgwr1 = tm_shape(utla_shp) +
    +  tm_fill(col = "fmb_localR2", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_fbgwr1 + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    -

    +
    +

    +
    +

    The map shows very high in-sample model predictions of up to 80% in relatively large UTLAs (i.e. Cornwall, Devon and Cumbria) but poor predictions in Linconshire and small UTLAs in the North West and Yorkshire & The Humber Regions and the Greater London. The spatial distribution of this pattern may reflect a potential problem that arise in the application of GWR with fixed spatial kernels. The use of fixed kernels implies that local regressions for small spatial units may be calibrated on a large number of dissimilar areas, while local regressions for large areas may be calibrated on very few data points, giving rise to estimates with large standard errors. In extreme cases, generating estimates might not be possible due to insufficient variation in small samples. In practice, this issue is relatively common if the number of geographical areas in the dataset is small.

    -

    -
    -

    9.6.4.2 Adaptive Bandwidth

    +

    +9.6.4.2 Adaptive Bandwidth

    To reduce these problems, adaptive spatial kernels can be used. These kernels adapt in size to variations in the density of the data so that the kernels have larger bandwidths where the data are sparse and have smaller bandwidths where the data are plentiful. As above, we first need to search for the optimal bandwidth before estimating a GWR.

    -
    # find optimal kernel bandwidth using cross validation
    -abw <- gwr.sel(eq1, 
    -               data = utla_shp, 
    -               coords=cbind( long, lat),
    -               longlat = TRUE,
    -               adapt = TRUE, 
    -               gweight = gwr.Gauss, 
    -               verbose = FALSE)
    -
    -# view selected bandwidth
    -abw
    +
    # find optimal kernel bandwidth using cross validation
    +abw <- gwr.sel(eq1, 
    +               data = utla_shp, 
    +               coords=cbind( long, lat),
    +               longlat = TRUE,
    +               adapt = TRUE, 
    +               gweight = gwr.Gauss, 
    +               verbose = FALSE)
    +
    +# view selected bandwidth
    +abw
    [1] 0.03126972

    The optimal bandwidth is 0.03 indicating the proportion of observations (or k-nearest neighbours) to be included in the weighting scheme. In this example, the optimal bandwidth indicates that for a given UTLA, 3% of its nearest neighbours should be used to calibrate the relevant local regression; that is about 5 UTLAs. The search window will thus be variable in size depending on the extent of UTLAs. Note that here the optimal bandwidth is defined based on a data point’s k-nearest neighbours. It can also be defined by geographical distance as done above for the fixed spatial kernel. We next fit a GWR based on an adaptive bandwidth.

    -
    # fit a gwr based on adaptive bandwidth
    -ab_gwr <- gwr(eq1, 
    -            data = utla_shp,
    -            coords=cbind( long, lat),
    -            longlat = TRUE,
    -            adapt = abw, 
    -            gweight = gwr.Gauss,
    -            hatmatrix=TRUE, 
    -            se.fit=TRUE)
    -
    -ab_gwr
    +
    # fit a gwr based on adaptive bandwidth
    +ab_gwr <- gwr(eq1, 
    +            data = utla_shp,
    +            coords=cbind( long, lat),
    +            longlat = TRUE,
    +            adapt = abw, 
    +            gweight = gwr.Gauss,
    +            hatmatrix=TRUE, 
    +            se.fit=TRUE)
    +
    +ab_gwr
    Call:
     gwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), 
    @@ -750,96 +757,101 @@ 

    < Quasi-global R2: 0.7845551

    -
    -
    -
    -

    9.6.5 Model fit

    +

    +9.6.5 Model fit

    Assessing the global fit of the model, marginal improvements are observed. The \(AIC\) and Residual sum of squares experienced marginal reductions, while the \(R^{2}\) increased compared to the GRW based on a fixed kernel. To gain a better understanding of these changes, as above, we map the \(R^{2}\) values for the estimated local regressions.

    -
    # write gwr output into a data frame
    -ab_gwr_out <- as.data.frame(ab_gwr$SDF)
    -
    -utla_shp$amb_ethnic <- ab_gwr_out$ethnic
    -utla_shp$amb_lt_illness <- ab_gwr_out$lt_illness
    -utla_shp$amb_localR2 <- ab_gwr_out$localR2
    -
    -# map
    -  # Local R2
    -legend_title = expression("Adaptive: Local R2")
    -map_abgwr1 = tm_shape(utla_shp) +
    -  tm_fill(col = "amb_localR2", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_abgwr1 + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    +
    # write gwr output into a data frame
    +ab_gwr_out <- as.data.frame(ab_gwr$SDF)
    +
    +utla_shp$amb_ethnic <- ab_gwr_out$ethnic
    +utla_shp$amb_lt_illness <- ab_gwr_out$lt_illness
    +utla_shp$amb_localR2 <- ab_gwr_out$localR2
    +
    +# map
    +  # Local R2
    +legend_title = expression("Adaptive: Local R2")
    +map_abgwr1 = tm_shape(utla_shp) +
    +  tm_fill(col = "amb_localR2", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_abgwr1 + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    -

    +
    +

    +
    +

    The map reveals notable improvements in local estimates for UTLAs within West and East Midlands, the South East, South West and East of England. Estimates are still poor in hot spot UTLAs concentrating confirmed cases of COVID-19, such as the Greater London, Liverpool and Newcastle areas.

    -
    -
    -

    9.6.6 Interpretation

    +

    +9.6.6 Interpretation

    The key strength of GWR models is in identifying patterns of spatial variation in the associations between pairs of variables. The results reveal how these coefficients vary across the 150 UTLAs of England. To examine this variability, let’s first focus on the adaptive GWR output reported in Section 8.6.4.2. The output includes a summary of GWR coefficient estimates at various data points. The last column reports the global estimates which are the same as the coefficients from the OLS regression we fitted at the start of our analysis. For our variable nonwhite ethnic population, the GWR outputs reveals that local coefficients range from a minimum value of -148.41 to a maximum value of 1076.84, indicating that one percentage point increase in the share of nonwhite ethnic population is associated with a a reduction of 148.41 in the number of cumulative confirmed cases of COVID-19 per 100,000 people in some UTLAs and an increase of 1076.84 in others. For half of the UTLAs in the dataset, as the share of nonwhite ethnic population increases by one percentage point, the rate of COVID-19 will increase between 106.29 and 291.24 cases; that is, the inter-quartile range between the 1st Qu and the 3rd Qu. To analyse the spatial structure, we next map the estimated coefficients obtained from the adaptive kernel GWR.

    -
      # Ethnic
    -legend_title = expression("Ethnic")
    -map_abgwr2 = tm_shape(utla_shp) +
    -  tm_fill(col = "amb_ethnic", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_abgwr2 = map_abgwr2 + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    -
    -  # Long-term Illness
    -legend_title = expression("Long-term illness")
    -map_abgwr3 = tm_shape(utla_shp) +
    -  tm_fill(col = "amb_lt_illness", title = legend_title, palette = magma(256), style = "cont") + # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white") # change background colour
    -map_abgwr3 = map_abgwr3 + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    -
    -tmap_arrange(map_abgwr2, map_abgwr3)
    +
      # Ethnic
    +legend_title = expression("Ethnic")
    +map_abgwr2 = tm_shape(utla_shp) +
    +  tm_fill(col = "amb_ethnic", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_abgwr2 = map_abgwr2 + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    +
    +  # Long-term Illness
    +legend_title = expression("Long-term illness")
    +map_abgwr3 = tm_shape(utla_shp) +
    +  tm_fill(col = "amb_lt_illness", title = legend_title, palette = magma(256), style = "cont") + # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white") # change background colour
    +map_abgwr3 = map_abgwr3 + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    +
    +tmap_arrange(map_abgwr2, map_abgwr3)
    -

    +
    +

    +
    +

    Analysing the map for long-term illness, a clear North-South divide can be identified. In the North we observed the expected positive relationship between COVID-19 and long-term illness i.e. as the share of the local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to increase. In the South, we observe the inverse pattern i.e. as the share of local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to drop. This pattern is counterintuitive but may be explained by the wider socio-economic disadvantages between the North and the South of England. The North is usually characterised by a persistent concentration of more disadvantaged neighbourhoods than the South where affluent households have tended to cluster for the last 40 years (Patias, Rowe, and Arribas-Bel 2021).

    -
    -
    -

    9.6.7 Assessing statistical significance

    +

    +9.6.7 Assessing statistical significance

    While the maps above offer valuable insights to understand the spatial pattering of relationships, they do not identify whether these associations are statistically significant. They may not be. Roughly, if a coefficient estimate has an absolute value of t greater than 1.96 and the sample is sufficiently large, then it is statistically significant. Our sample has only 150 observations, so we are more conservative and considered a coefficient to be statistically significant if it has an absolute value of t larger than 2. Note also that p-values could be computed - see Lu et al. (2014).

    -
    # compute t statistic
    -utla_shp$t_ethnic = ab_gwr_out$ethnic / ab_gwr_out$ethnic_se
    -
    -# categorise t values
    -utla_shp$t_ethnic_cat <- cut(utla_shp$t_ethnic,
    -                     breaks=c(min(utla_shp$t_ethnic), -2, 2, max(utla_shp$t_ethnic)),
    -                     labels=c("sig","nonsig", "sig"))
    -
    -# map statistically significant coefs for ethnic
    -legend_title = expression("Ethnic: significant")
    -map_sig = tm_shape(utla_shp) + 
    -  tm_fill(col = "t_ethnic_cat", title = legend_title, legend.hist = TRUE, midpoint = NA, textNA = "", colorNA = "white") +  # add fill
    -  tm_borders(col = "white", lwd = .1)  + # add borders
    -  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    -  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    -  tm_layout(bg.color = "white", legend.outside = TRUE) # change background colour & place legend outside
    -
    -map_sig + tm_shape(reg_shp) + # add region boundaries
    -  tm_borders(col = "white", lwd = .5) # add borders
    +
    # compute t statistic
    +utla_shp$t_ethnic = ab_gwr_out$ethnic / ab_gwr_out$ethnic_se
    +
    +# categorise t values
    +utla_shp$t_ethnic_cat <- cut(utla_shp$t_ethnic,
    +                     breaks=c(min(utla_shp$t_ethnic), -2, 2, max(utla_shp$t_ethnic)),
    +                     labels=c("sig","nonsig", "sig"))
    +
    +# map statistically significant coefs for ethnic
    +legend_title = expression("Ethnic: significant")
    +map_sig = tm_shape(utla_shp) + 
    +  tm_fill(col = "t_ethnic_cat", title = legend_title, legend.hist = TRUE, midpoint = NA, textNA = "", colorNA = "white") +  # add fill
    +  tm_borders(col = "white", lwd = .1)  + # add borders
    +  tm_compass(type = "arrow", position = c("right", "top") , size = 5) + # add compass
    +  tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position =  c("center", "bottom")) + # add scale bar
    +  tm_layout(bg.color = "white", legend.outside = TRUE) # change background colour & place legend outside
    +
    +map_sig + tm_shape(reg_shp) + # add region boundaries
    +  tm_borders(col = "white", lwd = .5) # add borders
    -

    +
    +

    +
    +
    -
    # utla count
    -table(utla_shp$t_ethnic_cat)
    +
    # utla count
    +table(utla_shp$t_ethnic_cat)
    
        sig nonsig 
    @@ -850,20 +862,17 @@ 

    Challenge 3 Compute the t values for the intercept and estimated coefficient for long-term illness and create maps of their statistical significance. How many UTLAs report statistically significant coefficients?

    -

    -
    -

    9.6.8 Collinearity in GWR

    +

    +9.6.8 Collinearity in GWR

    An important final note is: collinearity tends to be problematic in GWR models. It can be present in the data subsets to estimate local coefficients even when not observed globally Wheeler and Tiefelsdorf (2005). Collinearity can be highly problematic in the case of compositional, categorical and ordinal predictors, and may result in exact local collinearity making the search for an optimal bandwidth impossible. A recent paper suggests potential ways forward (Comber et al. 2022).

    -
    -
    -
    -

    9.7 Questions

    -

    We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

    +

    +9.7 Questions

    +

    We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

    -
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    +
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    Reading layer `covid19_eng' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' 
       using driver `GPKG'
     Simple feature collection with 149 features and 507 fields
     Geometry type: MULTIPOLYGON
    @@ -884,39 +893,32 @@ 

    -
    -
    -
      -
    1. Read the file in R by executing read_tsv("data/gwr/readme.txt"). Ensure the library readr is installed before running read_tsv.99079↩︎

    2. -
    -
    - -

    - -
    + \ No newline at end of file diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-10-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-10-1.png index 027bd43..0bfd7ab 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-10-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-10-1.png differ diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-13-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-13-1.png index 7e0e9ec..361ac70 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-13-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-13-1.png differ diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-16-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-16-1.png index 999a99c..8c2a70a 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-16-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-16-1.png differ diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-17-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-17-1.png index 9a683f0..5559df2 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-17-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-17-1.png differ diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-18-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-18-1.png index 221f46f..e0f7fe2 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-18-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-18-1.png differ diff --git a/docs/09-gwr_files/figure-html/unnamed-chunk-4-1.png b/docs/09-gwr_files/figure-html/unnamed-chunk-4-1.png index 5802025..d409292 100644 Binary files a/docs/09-gwr_files/figure-html/unnamed-chunk-4-1.png and b/docs/09-gwr_files/figure-html/unnamed-chunk-4-1.png differ diff --git a/docs/10-st_analysis.html b/docs/10-st_analysis.html index 7cf7d87..4c79d13 100644 --- a/docs/10-st_analysis.html +++ b/docs/10-st_analysis.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 10  Spatio-Temporal Analysis - +} @@ -122,14 +90,19 @@ - - - - - - +} + - - +
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    10  Spatio-Temporal Analysis

    +
    +

    10  Spatio-Temporal Analysis

    @@ -308,51 +319,49 @@

    Cressie and Johannesson (2008). It enables constructing a spatial random effects model on a discretised spatial domain. Key advantages of this approach comprise the capacity to: (1) work with large data sets, (2) be scaled up; (3) generate predictions based on sparse linear algebraic techniques, and (4) produce fine-scale resolution uncertainty estimates.

    +

    This chapter provides an introduction to the complexities of spatio-temporal data and modelling. For modelling, we consider the Fixed Rank Kriging (FRK) framework developed by Cressie and Johannesson (2008). It enables constructing a spatial random effects model on a discretised spatial domain. Key advantages of this approach comprise the capacity to: (1) work with large data sets, (2) be scaled up; (3) generate predictions based on sparse linear algebraic techniques, and (4) produce fine-scale resolution uncertainty estimates.

    The content of this chapter is based on:

    • Wikle, Zammit-Mangion, and Cressie (2019), a recently published book which provides a good overview of existing statistical approaches to spatio-temporal modelling and R packages.

    • Zammit-Mangion and Cressie (2017), who introduce the statistical framework and R package for modelling spatio-temporal used in this Chapter.

    -
    -

    10.1 Dependencies

    +

    +10.1 Dependencies

    This chapter uses the following libraries:

    -
    # Data manipulation, transformation and visualisation
    -library(tidyverse)
    -# Nice tables
    -library(kableExtra)
    -# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    -library(sf) 
    -# Spatial objects conversion
    -library(sp) 
    -# Thematic maps
    -library(tmap) 
    -# Nice colour schemes
    -library(viridis) 
    -# Obtain correlation coefficients
    -library(corrplot)
    -# Highlight data on plots
    -library(gghighlight)
    -# Analysing spatio-temporal data
    -#library(STRbook)
    -library(spacetime)
    -# Date parsing and manipulation
    -library(lubridate)
    -# Applied statistics
    -library(MASS)
    -# Statistical tests for linear regression models
    -library(lmtest)
    -# Fit spatial random effects models
    -library(FRK)
    -# Exportable regression tables
    -library(jtools)
    -
    -
    -
    -

    10.2 Data

    +
    # Data manipulation, transformation and visualisation
    +library(tidyverse)
    +# Nice tables
    +library(kableExtra)
    +# Simple features (a standardised way to encode vector data ie. points, lines, polygons)
    +library(sf) 
    +# Spatial objects conversion
    +library(sp) 
    +# Thematic maps
    +library(tmap) 
    +# Nice colour schemes
    +library(viridis) 
    +# Obtain correlation coefficients
    +library(corrplot)
    +# Highlight data on plots
    +library(gghighlight)
    +# Analysing spatio-temporal data
    +#library(STRbook)
    +library(spacetime)
    +# Date parsing and manipulation
    +library(lubridate)
    +# Applied statistics
    +library(MASS)
    +# Statistical tests for linear regression models
    +library(lmtest)
    +# Fit spatial random effects models
    +library(FRK)
    +# Exportable regression tables
    +library(jtools)
    +
    +

    +10.2 Data

    For this chapter, we will use data on:

    • COVID-19 confirmed cases from 30th January, 2020 to 21st April, 2020 from Public Health England via the GOV.UK dashboard;

    • @@ -360,15 +369,14 @@

      GOV.UK and published by the Ministry of Housing, Communities & Local Government. The data are at the ONS Upper Tier Local Authority (UTLA) level - also known as Counties and Unitary Authorities.

    For a full list of the variables included in the data sets used in this chapter, see the readme file in the sta data folder.1. Before we get our hands on the data, there are some important concepts that need to be introduced. They provide a useful framework to understand the complex structure of spatio-temporal data. Let’s start by first highlighling the importance of spatio-temporal analysis.

    -
    -
    -

    10.3 Why Spatio-Temporal Analysis?

    +

    1 Read the file in R by executing read_tsv("data/sta/readme.txt"). Ensure the library readr is installed before running read_tsv.

    +10.3 Why Spatio-Temporal Analysis?

    Investigating the spatial patterns of human processes as we have done so far in this book only offers a partial incomplete representation of these processes. It does not allow understanding of the temporal evolution of these processes. Human processes evolve in space and time. Human mobility is a inherent geographical process which changes over the course of the day, with peaks at rush hours and high concentration towards employment, education and retail centres. Exposure to air pollution changes with local climatic conditions, and emission and concentration of atmospheric pollutants which fluctuate over time. The rate of disease spread varies over space and may significantly change over time as we have seen during the current outbreak, with flattened or rapid declining trends in Australia, New Zealand and South Korea but fast proliferation in the United Kingdom and the United States. Only by considering time and space together we can address how geographic entities change over time and why they change. A large part of how and why of such change occurs is due to interactions across space and time, and multiple processes. It is essential to understand the past to inform our understanding of the present and make predictions about the future.

    -
    -

    10.3.1 Spatio-temporal Data Structures

    +

    +10.3.1 Spatio-temporal Data Structures

    A first key element is to understand the structure of spatio-temporal data. Spatio-temporal data incorporate two dimensions. At one end, we have the temporal dimension. In quantitative analysis, time-series data are used to capture geographical processes at regular or irregular intervals; that is, in a continuous (daily) or discrete (only when a event occurs) temporal scale. At another end, we have the spatial dimension. We often use spatial data as temporal aggregations or temporally frozen states (or ‘snapshots’) of a geographical process - this is what we have done so far. Recall that spatial data can be capture in different geographical units, such as areal or lattice, points, flows or trajectories - refer to the introductory lecture in Week 1. Relatively few ways exist to formally integrate temporal and spatial data in consistent analytical framework. Two notable exceptions in R are the packages TraMiner (Gabadinho et al. 2009) and spacetime (Pebesma et al. 2012). We use the class definitions defined in the R package spacetime. These classes extend those used for spatial data in sp and time-series data in xts. Next a brief introduction to concepts that facilitate thinking about spatio-temporal data structures.

    -
    -

    10.3.1.1 Type of Table

    +

    +10.3.1.1 Type of Table

    Spatio-temporal data can be conceptualised as three main different types of tables:

    • time-wide: a table in which columns correspond to different time points

    • @@ -378,9 +386,8 @@

      Note that data in long format are space inefficient because spatial coordinates and time attributes are required for each data point. Yet, data in this format are relatively easy to manipulate via packages such as dplyr and tidyr, and visualise using ggplot2. These packages are designed to work with data in long format.

      -

    -
    -

    10.3.1.2 Type of Spatio-Temporal Object

    +

    +10.3.1.2 Type of Spatio-Temporal Object

    To integrate spatio-temporal data, spatio-temporal objects are needed. We consider four different spatio-temporal frames (STFs) or objects which can be defined via the package spacetime:

    • Full grid (STF): an object containing data on all possible locations in all time points in a sequence of data;

    • @@ -389,11 +396,8 @@

      Pebesma et al. (2012). Enough theory, let’s code!

      -

    -
    -
    -
    -

    10.4 Data Wrangling

    +

    +10.4 Data Wrangling

    This section illustrates the complexities of handling spatio-temporal data. It discusses good practices in data manipulation and construction of a Space Time Irregular Data Frame (STIDF) object. Three key requirements to define a STFDF object are:

    1. Have a data frame in long format i.e. a location-time pair data frame

    2. @@ -408,14 +412,14 @@

      These data objects correspond to locs, time, and covid19 and censusimd below. Throughout the chapter you will notice that we switch between the various data frames when convinient, depending on the operation.

      -
      # clear workspace
      -rm(list=ls())
      -
      -# read ONS UTLA shapefile
      -utla_shp <- st_read("data/sta/ons_utla.shp") 
      +
      # clear workspace
      +rm(list=ls())
      +
      +# read ONS UTLA shapefile
      +utla_shp <- st_read("data/sta/ons_utla.shp") 
      Reading layer `ons_utla' from data source 
      -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/sta/ons_utla.shp' 
      +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/sta/ons_utla.shp' 
         using driver `ESRI Shapefile'
       Simple feature collection with 150 features and 11 fields
       Geometry type: MULTIPOLYGON
      @@ -423,31 +427,31 @@ 

      -
      # create table of locations
      -locs <- utla_shp %>% as.data.frame() %>%
      -  dplyr::select(objct, cty19c, ctyu19nm, long, lat, st_rs) 
      -
      -# read time data frame
      -time <- read_csv("data/sta/reporting_dates.csv")
      -
      -# read COVID-19 data in long format
      -covid19 <- read_csv("data/sta/covid19_cases.csv")
      -
      -# read census and IMD data
      -censusimd <- read_csv("data/sta/2011census_2019imd_utla.csv")
      +
      # create table of locations
      +locs <- utla_shp %>% as.data.frame() %>%
      +  dplyr::select(objct, cty19c, ctyu19nm, long, lat, st_rs) 
      +
      +# read time data frame
      +time <- read_csv("data/sta/reporting_dates.csv")
      +
      +# read COVID-19 data in long format
      +covid19 <- read_csv("data/sta/covid19_cases.csv")
      +
      +# read census and IMD data
      +censusimd <- read_csv("data/sta/2011census_2019imd_utla.csv")

      If we explore the structure of the data via head and str, we can see we have data on daily and cumulative new COVID-19 cases for 150 spatial units (i.e. UTLAs) over 71 time points from January 30th to April 21st. We also have census and IMD data for a range of attributes.

      -
      head(covid19, 3)
      +
      head(covid19, 3)
      # A tibble: 3 × 6
      -  Area.name            Area.code Area.type            date       Daily…¹ Cumul…²
      -  <chr>                <chr>     <chr>                <date>       <dbl>   <dbl>
      -1 Barking and Dagenham E09000002 Upper tier local au… 2020-01-30       0       0
      -2 Barnet               E09000003 Upper tier local au… 2020-01-30       0       0
      -3 Barnsley             E08000016 Upper tier local au… 2020-01-30       0       0
      -# … with abbreviated variable names ¹​Daily.lab.confirmed.cases,
      -#   ²​Cumulative.lab.confirmed.cases
      + Area.name Area.code Area.type date Daily.lab.confirmed.…¹ + <chr> <chr> <chr> <date> <dbl> +1 Barking and Dagenham E09000002 Upper tier l… 2020-01-30 0 +2 Barnet E09000003 Upper tier l… 2020-01-30 0 +3 Barnsley E08000016 Upper tier l… 2020-01-30 0 +# ℹ abbreviated name: ¹​Daily.lab.confirmed.cases +# ℹ 1 more variable: Cumulative.lab.confirmed.cases <dbl>

      Once we have understood the structure of the data, we first need to confirm if the covid19 data are in wide or long format. Luckily they are in long format; otherwise, we would have needed to transform the data from wide to long format. Useful functions to achieve this include pivot_longer (pivot_longer) which has superseded gather (spread) in the tidyr package. Note that the covid19 data frame has 10,650 observations (i.e. rows); that is, 150 UTLAs * 71 daily observations.

      @@ -456,46 +460,46 @@

      Note that working with dates can be a complex task. A good discussion of these complexities is provided here.

      -
      # check the time structure used for reporting covid cases
      -head(covid19$date, 5)
      +
      # check the time structure used for reporting covid cases
      +head(covid19$date, 5)
      [1] "2020-01-30" "2020-01-30" "2020-01-30" "2020-01-30" "2020-01-30"
      -
      # parsing data into a time stamp
      -covid19$date <- ymd(covid19$date)
      -class(covid19$date)
      +
      # parsing data into a time stamp
      +covid19$date <- ymd(covid19$date)
      +class(covid19$date)
      [1] "Date"
      -
      # separate date variable into day,week, month and year variables
      -covid19$day <- day(covid19$date)
      -covid19$week <- week(covid19$date) # week of the year
      -covid19$month <- month(covid19$date)
      -covid19$year <- year(covid19$date)
      +
      # separate date variable into day,week, month and year variables
      +covid19$day <- day(covid19$date)
      +covid19$week <- week(covid19$date) # week of the year
      +covid19$month <- month(covid19$date)
      +covid19$year <- year(covid19$date)

      Once defined the time stamp, we need to add the spatial information contained in our shapefile to create a spatio-temporal data frame.

      -
      # join dfs
      -covid19_spt <- left_join(utla_shp, covid19, by = c("ctyu19nm" = "Area.name"))
      +
      # join dfs
      +covid19_spt <- left_join(utla_shp, covid19, by = c("ctyu19nm" = "Area.name"))

      We now have all the components to build a spatio-temporal object of class STIDF using STIDF from the spacetime package:

      -
      # identifying spatial fields
      -spat_part <- as(dplyr::select(covid19_spt, -c(bng_e, bng_n, Area.code, Area.type, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, date, day, week, month, year)), Class = "Spatial")
      -
      -# identifying temporal fields
      -temp_part <- covid19_spt$date
      -
      -# identifying data
      -covid19_data <- covid19_spt %>% dplyr::select(c(Area.code, Area.type, date, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, day, week, month, year)) %>%
      -  as.data.frame()
      -
      -# construct STIDF object
      -covid19_stobj <- STIDF(sp = spat_part, # spatial fields
      -                time = temp_part, # time fields
      -                data = covid19_data) # data
      -                
      -class(covid19_stobj)
      +
      # identifying spatial fields
      +spat_part <- as(dplyr::select(covid19_spt, -c(bng_e, bng_n, Area.code, Area.type, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, date, day, week, month, year)), Class = "Spatial")
      +
      +# identifying temporal fields
      +temp_part <- covid19_spt$date
      +
      +# identifying data
      +covid19_data <- covid19_spt %>% dplyr::select(c(Area.code, Area.type, date, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, day, week, month, year)) %>%
      +  as.data.frame()
      +
      +# construct STIDF object
      +covid19_stobj <- STIDF(sp = spat_part, # spatial fields
      +                time = temp_part, # time fields
      +                data = covid19_data) # data
      +                
      +class(covid19_stobj)
      [1] "STIDF"
       attr(,"package")
      @@ -504,163 +508,169 @@ 

      We now add census and IMD variables. For the purposes of this Chapter, we only add total population and long-term sick or disabled population counts. You can add more variables by adding their names in the select function.

      -
      # select pop data
      -pop <- censusimd %>% dplyr::select("UTLA19NM", "Residents", "Longterm_sick_or_disabled")
      -# join dfs
      -covid19_spt <- left_join(covid19_spt, pop,
      -                         by = c("ctyu19nm" = "UTLA19NM"))
      -covid19 <- left_join(covid19, pop, by = c("Area.name" = "UTLA19NM"))
      -
      -

    -
    -

    10.5 Exploring Spatio-Temporal Data

    +
    # select pop data
    +pop <- censusimd %>% dplyr::select("UTLA19NM", "Residents", "Longterm_sick_or_disabled")
    +# join dfs
    +covid19_spt <- left_join(covid19_spt, pop,
    +                         by = c("ctyu19nm" = "UTLA19NM"))
    +covid19 <- left_join(covid19, pop, by = c("Area.name" = "UTLA19NM"))
    +
    +

    +10.5 Exploring Spatio-Temporal Data

    We now have all the required data in place. In this section various methods of data visualisation are illustrated before key dimensions of the data are explored. Both of these types of exploration can be challenging as one or more dimensions in space and one in time need to be interrogated.

    -
    -

    10.5.1 Visualisation

    +

    +10.5.1 Visualisation

    In the context spatio-temporal data, a first challenge is data visualization. Visualising more than two dimensions of spatio-temporal data, so it is helpful to slice or aggregate the data over a dimension, use color, or build animations through time. Before exploring the data, we need to define our key variable of interest; that is, the number of confirmed COVID-19 cases per 100,000 people. We also compute the cumulative number of confirmed COVID-19 cases per 100,000 people as it may be handy in some analyses.

    Fisrt create variable to be analysed:

    -
    # rate of new covid-19 infection
    -covid19_spt$n_covid19_r <- round( (covid19_spt$Daily.lab.confirmed.cases / covid19_spt$Residents) * 100000)
    -covid19$n_covid19_r <- round( (covid19$Daily.lab.confirmed.cases / covid19$Residents) * 100000 )
    -
    -# risk of cumulative covid-19 infection
    -covid19_spt$c_covid19_r <- round( (covid19_spt$Cumulative.lab.confirmed.cases / covid19_spt$Residents) * 100000)
    -covid19$c_covid19_r <- round( (covid19$Cumulative.lab.confirmed.cases / covid19$Residents) * 100000)
    -
    -
    -

    10.5.1.1 Spatial Plots

    +
    # rate of new covid-19 infection
    +covid19_spt$n_covid19_r <- round( (covid19_spt$Daily.lab.confirmed.cases / covid19_spt$Residents) * 100000)
    +covid19$n_covid19_r <- round( (covid19$Daily.lab.confirmed.cases / covid19$Residents) * 100000 )
    +
    +# risk of cumulative covid-19 infection
    +covid19_spt$c_covid19_r <- round( (covid19_spt$Cumulative.lab.confirmed.cases / covid19_spt$Residents) * 100000)
    +covid19$c_covid19_r <- round( (covid19$Cumulative.lab.confirmed.cases / covid19$Residents) * 100000)
    +
    +

    +10.5.1.1 Spatial Plots

    One way to visualise the data is using spatial plots; that is, snapshots of a geographic process for a given time period. Data can be mapped in different ways using clorepleth, countour or surface plots. The key aim of these maps is to understand how the overall extent of spatial variation and local patterns of spatial concentration change over time. Below we visualise the weekly number of confirmed COVID-19 cases per 100,000 people.

    Note that Weeks range from 5 to 16 as they refer to calendar weeks. Calendar week 5 is when the first COVID-19 case in England was reported.

    -
    # create data frame for new cases by week
    -daycases_week <- covid19_spt %>% 
    -  group_by(week, ctyu19nm, 
    -           as.character(cty19c), 
    -           Residents) %>%
    -  summarise(n_daycases = sum(Daily.lab.confirmed.cases)) 
    -
    -# weekly rate of new covid-19 infection
    -daycases_week$wn_covid19_r <- (daycases_week$n_daycases / daycases_week$Residents) * 100000
    -
    -# map
    -legend_title = expression("Cumulative Cases per 100,000 Population")
    -tm_shape(daycases_week) +
    -  tm_fill("wn_covid19_r", title = legend_title, palette = magma(256), style ="cont", legend.hist=FALSE, legend.is.portrait=FALSE) +
    -  tm_facets(by = "week", ncol = 4) +
    -  tm_borders(col = "white", lwd = .1)  + # add borders +
    -  tm_layout(bg.color = "white", # change background colour
    -            legend.outside = TRUE, # legend outside
    -            legend.outside.position = "bottom",
    -            legend.stack = "horizontal",
    -            legend.title.size = 2,
    -            legend.width = 1,
    -            legend.height = 1,
    -            panel.label.size = 3,
    -            main.title = "New COVID-19 Cases by Calendar Week, UTLA, England") 
    +
    # create data frame for new cases by week
    +daycases_week <- covid19_spt %>% 
    +  group_by(week, ctyu19nm, 
    +           as.character(cty19c), 
    +           Residents) %>%
    +  summarise(n_daycases = sum(Daily.lab.confirmed.cases)) 
    +
    +# weekly rate of new covid-19 infection
    +daycases_week$wn_covid19_r <- (daycases_week$n_daycases / daycases_week$Residents) * 100000
    +
    +# map
    +legend_title = expression("Cumulative Cases per 100,000 Population")
    +tm_shape(daycases_week) +
    +  tm_fill("wn_covid19_r", title = legend_title, palette = magma(256), style ="cont", legend.hist=FALSE, legend.is.portrait=FALSE) +
    +  tm_facets(by = "week", ncol = 4) +
    +  tm_borders(col = "white", lwd = .1)  + # add borders +
    +  tm_layout(bg.color = "white", # change background colour
    +            legend.outside = TRUE, # legend outside
    +            legend.outside.position = "bottom",
    +            legend.stack = "horizontal",
    +            legend.title.size = 2,
    +            legend.width = 1,
    +            legend.height = 1,
    +            panel.label.size = 3,
    +            main.title = "New COVID-19 Cases by Calendar Week, UTLA, England") 
    Warning in pre_process_gt(x, interactive = interactive, orig_crs =
     gm$shape.orig_crs): legend.width controls the width of the legend within a map.
     Please use legend.outside.size to control the width of the outside legend
    -

    +
    +

    +
    +

    The series of maps reveal a stable pattern of low reported cases from calendar weeks 5 to 11. From week 12 a number of hot spots emerged, notably in London, Birmingham, Cumbria and subsequently around Liverpool. The intensity of new cases seem to have started to decline from week 15; yet, it is important to note that week 16 display reported cases for only two days.

    -
    -
    -

    10.5.1.2 Time-Series Plots

    +

    +10.5.1.2 Time-Series Plots

    Time-series plots can be used to capture a different dimension of the process in analysis. They can be used to better understand changes in an observation location, an aggregation of observations, or multiple locations simultaneously over time. We plot the cumulative number of COVID-19 cases per 100,000 people for UTLAs reporting over 310 cases. The plots identify the UTLAs in London, Newcastle and Sheffield reporting the largest numbers of COVID-19 cases. The plots also reveal that there has been a steady increase in the number of cases, with some differences. While cases have steadily increase in Brent and Southwark since mid March, the rise has been more sudden in Sunderland. The plots also reveal a possible case of misreporting in Sutton towards the end of the series.

    -
    tsp <- ggplot(data = covid19_spt,
    -            mapping = aes(x = date, y = c_covid19_r,
    -                          group = ctyu19nm))
    -tsp + geom_line(color = "blue") + 
    -    gghighlight(max(c_covid19_r) > 310, use_direct_label = FALSE) +
    -    labs(title= paste(" "), x="Date", y="Cumulative Cases per 100,000") +
    -    theme_classic() +
    -    theme(plot.title=element_text(size = 20)) +
    -    theme(axis.text=element_text(size=16)) +
    -    theme(axis.title.y = element_text(size = 18)) +
    -    theme(axis.title.x = element_text(size = 18)) +
    -    theme(plot.subtitle=element_text(size = 16)) +
    -    theme(axis.title=element_text(size=20, face="plain")) +
    -    facet_wrap(~ ctyu19nm)
    +
    tsp <- ggplot(data = covid19_spt,
    +            mapping = aes(x = date, y = c_covid19_r,
    +                          group = ctyu19nm))
    +tsp + geom_line(color = "blue") + 
    +    gghighlight(max(c_covid19_r) > 310, use_direct_label = FALSE) +
    +    labs(title= paste(" "), x="Date", y="Cumulative Cases per 100,000") +
    +    theme_classic() +
    +    theme(plot.title=element_text(size = 20)) +
    +    theme(axis.text=element_text(size=16)) +
    +    theme(axis.title.y = element_text(size = 18)) +
    +    theme(axis.title.x = element_text(size = 18)) +
    +    theme(plot.subtitle=element_text(size = 16)) +
    +    theme(axis.title=element_text(size=20, face="plain")) +
    +    facet_wrap(~ ctyu19nm)
    -

    +
    +

    +
    -
    -
    -

    10.5.1.3 Hovmöller Plots

    +
    +

    +10.5.1.3 Hovmöller Plots

    An alternative visualisation is a Hovmöller plot - sometimes known as heatmap. It is a two-dimensional space-time representation in which space is collapsed onto one dimension against time. Hovmöller plots can easily be generated if the data are arranged on a space-time grid; however, this is rarely the case. Luckily we have ggplot! which can do magic rearranging the data as needed. Below we produce a Hovmöller plot for UTLAs with resident populations over 260,000. The plot makes clear that the critical period of COVID-19 spread has been during April despite the implementation of a series of social distancing measures by the government.

    -
    ggplot(data = dplyr::filter(covid19_spt, Residents > 260000), 
    -           mapping = aes(x= date, y= reorder(ctyu19nm, c_covid19_r), fill= c_covid19_r)) +
    -  geom_tile() +
    -  scale_fill_viridis(name="New Cases per 100,000", option ="plasma", begin = 0, end = 1, direction = 1) +
    -  theme_minimal() + 
    -  labs(title= paste(" "), x="Date", y="Upper Tier Authority Area") +
    -  theme(legend.position = "bottom") +
    -  theme(legend.title = element_text(size=15)) +
    -  theme(axis.text.y = element_text(size=10)) +
    -  theme(axis.text.x = element_text(size=15)) +
    -  theme(axis.title=element_text(size=20, face="plain")) +
    -  theme(legend.key.width = unit(5, "cm"), legend.key.height = unit(2, "cm"))
    +
    ggplot(data = dplyr::filter(covid19_spt, Residents > 260000), 
    +           mapping = aes(x= date, y= reorder(ctyu19nm, c_covid19_r), fill= c_covid19_r)) +
    +  geom_tile() +
    +  scale_fill_viridis(name="New Cases per 100,000", option ="plasma", begin = 0, end = 1, direction = 1) +
    +  theme_minimal() + 
    +  labs(title= paste(" "), x="Date", y="Upper Tier Authority Area") +
    +  theme(legend.position = "bottom") +
    +  theme(legend.title = element_text(size=15)) +
    +  theme(axis.text.y = element_text(size=10)) +
    +  theme(axis.text.x = element_text(size=15)) +
    +  theme(axis.title=element_text(size=20, face="plain")) +
    +  theme(legend.key.width = unit(5, "cm"), legend.key.height = unit(2, "cm"))
    -

    +
    +

    +
    +
    -
    -
    -

    10.5.1.4 Interactive Plots

    +

    +10.5.1.4 Interactive Plots

    Interactive visualisations comprise very effective ways to understand spatio-temporal data and they are now fairly accessible. Interactive visualisations allow for a more data-immersive experience, and enable exploration of the data without having to resort to scripting. Here is when the use of tmap shines as it does not only enables easily creating nice static maps but also interactive maps! Below an interactive map for a time snapshot of the data (i.e. 2020-04-14) is produced, but with a bit of work layers can be added to display multiple temporal slices of the data.

    -
    # map
    -legend_title = expression("Cumulative Cases per 100,000 Population")
    -imap = tm_shape(dplyr::filter(covid19_spt[,c("ctyu19nm", "date", "c_covid19_r")], as.character(date) == "2020-04-14"), labels = "Area.name") +
    -  tm_fill("c_covid19_r", title = legend_title, palette = magma(256), style ="cont", legend.is.portrait=FALSE, alpha = 0.7) +
    -  tm_borders(col = "white") +
    -  #tm_text("ctyu19nm", size = .4) +
    -  tm_layout(bg.color = "white", # change background colour
    -            legend.outside = TRUE, # legend outside
    -            legend.title.size = 1,
    -            legend.width = 1) 
    +
    # map
    +legend_title = expression("Cumulative Cases per 100,000 Population")
    +imap = tm_shape(dplyr::filter(covid19_spt[,c("ctyu19nm", "date", "c_covid19_r")], as.character(date) == "2020-04-14"), labels = "Area.name") +
    +  tm_fill("c_covid19_r", title = legend_title, palette = magma(256), style ="cont", legend.is.portrait=FALSE, alpha = 0.7) +
    +  tm_borders(col = "white") +
    +  #tm_text("ctyu19nm", size = .4) +
    +  tm_layout(bg.color = "white", # change background colour
    +            legend.outside = TRUE, # legend outside
    +            legend.title.size = 1,
    +            legend.width = 1) 

    To view the map on your local machines, execute the code chunk below removing the # sign.

    -
    #tmap_mode("view")
    -#imap
    +
    #tmap_mode("view")
    +#imap

    Alternative data visualisation tools are animations, telliscope and shiny. Animations can be constructed by plotting spatial data frame-by-frame, and then stringing them together in sequence. A useful R packages gganimate and tmap! See Lovelace, Nowosad, and Muenchow (2019). Note that the creation of animations may require external dependencies; hence, they have been included here. Both telliscope and shiny are useful ways for visualising large spatio-temporal data sets in an interactive ways. Some effort is required to deploy these tools.

    -
    - -
    -

    10.5.2 Exploratory Analysis

    +

    +10.5.2 Exploratory Analysis

    In addition to visualising data, we often want to obtain numerical summaries of the data. Again, innovative ways to reduce the inherent dimensionality of the data and examine dependence structures and potential relationships in time and space are needed. We consider visualisations of empirical spatial and temporal means, dependence structures and some basic time-series analysis.

    -
    -

    10.5.2.1 Means

    +

    +10.5.2.1 Means

    Empirical Spatial Mean

    The empirical spatial mean for a data set can be obtained by averaging over time points for one location. In our case, we can compute the empirical spatial mean by averaging the daily rate of new COVID-19 cases for UTLAs between January 30th and April 21st. It reveals that Brent, Southwark and Sunderland report an average daily infection rate of over 5 new cases per 100,000 people, whereas Rutland and Isle of Wight display an average of less than 1.

    -
    # compute empirical spatial mean
    -sp_av <- covid19_spt %>% group_by(ctyu19nm) %>% # group by spatial unit
    -  summarise(sp_mu_emp = mean(n_covid19_r))
    -
    -# plot empirical spatial mean
    -ggplot(data=sp_av) +
    -  geom_col( aes( y = reorder(ctyu19nm, sp_mu_emp), x = sp_mu_emp) , fill = "grey50") +
    -  theme_classic() +
    -  labs(title= paste(" "), x="Average New Cases per 100,000", y="Upper Tier Authority Area") +
    -  theme(legend.position = "bottom") +
    -  theme(axis.text.y = element_text(size=7)) +
    -  theme(axis.text.x = element_text(size=12)) +
    -  theme(axis.title=element_text(size=20, face="plain"))
    +
    # compute empirical spatial mean
    +sp_av <- covid19_spt %>% group_by(ctyu19nm) %>% # group by spatial unit
    +  summarise(sp_mu_emp = mean(n_covid19_r))
    +
    +# plot empirical spatial mean
    +ggplot(data=sp_av) +
    +  geom_col( aes( y = reorder(ctyu19nm, sp_mu_emp), x = sp_mu_emp) , fill = "grey50") +
    +  theme_classic() +
    +  labs(title= paste(" "), x="Average New Cases per 100,000", y="Upper Tier Authority Area") +
    +  theme(legend.position = "bottom") +
    +  theme(axis.text.y = element_text(size=7)) +
    +  theme(axis.text.x = element_text(size=12)) +
    +  theme(axis.title=element_text(size=20, face="plain"))
    -

    +
    +

    +
    +

    Empirical Temporal Mean

    @@ -669,35 +679,37 @@

    -
    # compute temporal mean
    -tm_av <- covid19 %>% group_by(date) %>%
    -  summarise(tm_mu_emp = mean(n_covid19_r))
    -
    -# plot temporal mean + trends for all spatial units
    -ggplot() +
    -  geom_line(data = covid19, mapping = aes(x =date, y = n_covid19_r,
    -                          group = Area.name), color = "gray80") +
    -   theme_classic() +
    -  geom_smooth(data = tm_av, mapping = aes(x =date, y = tm_mu_emp), 
    -              alpha = 0.5,
    -              se = FALSE) +
    -    labs(title= paste(" "), x="Date", y="Cumulative Cases per 100,000") +
    -    theme_classic() +
    -    theme(plot.title=element_text(size = 18)) +
    -    theme(axis.text=element_text(size=14)) +
    -    theme(axis.title.y = element_text(size = 16)) +
    -    theme(axis.title.x = element_text(size = 16)) +
    -    theme(plot.subtitle=element_text(size = 16)) +
    -    theme(axis.title=element_text(size=18, face="plain"))
    +
    # compute temporal mean
    +tm_av <- covid19 %>% group_by(date) %>%
    +  summarise(tm_mu_emp = mean(n_covid19_r))
    +
    +# plot temporal mean + trends for all spatial units
    +ggplot() +
    +  geom_line(data = covid19, mapping = aes(x =date, y = n_covid19_r,
    +                          group = Area.name), color = "gray80") +
    +   theme_classic() +
    +  geom_smooth(data = tm_av, mapping = aes(x =date, y = tm_mu_emp), 
    +              alpha = 0.5,
    +              se = FALSE) +
    +    labs(title= paste(" "), x="Date", y="Cumulative Cases per 100,000") +
    +    theme_classic() +
    +    theme(plot.title=element_text(size = 18)) +
    +    theme(axis.text=element_text(size=14)) +
    +    theme(axis.title.y = element_text(size = 16)) +
    +    theme(axis.title.x = element_text(size = 16)) +
    +    theme(plot.subtitle=element_text(size = 16)) +
    +    theme(axis.title=element_text(size=18, face="plain"))
    -

    +
    +

    +
    -

    -
    -

    10.5.2.2 Dependence

    +
    +

    +10.5.2.2 Dependence

    Spatial Dependence

    -

    As we know spatial dependence refers to the spatial relationship of a variable’s values for a pairs of locations at a certain distance apart, so that are more similar (or less similar) than expected for randomly associated pairs of observations. Patterns of spatial dependence may change over time. In the case of a disease outbreak patterns of spatial dependence can change very quickly as new cases emerge and social distancing measures are implemented. Chapter 6 illustrates how to measure spatial dependence in the context of spatial data.

    +

    As we know spatial dependence refers to the spatial relationship of a variable’s values for a pairs of locations at a certain distance apart, so that are more similar (or less similar) than expected for randomly associated pairs of observations. Patterns of spatial dependence may change over time. In the case of a disease outbreak patterns of spatial dependence can change very quickly as new cases emerge and social distancing measures are implemented. Chapter 6 illustrates how to measure spatial dependence in the context of spatial data.

    Challenge 1: Measure how spatial dependence change over time. Hint: compute the Moran’s I on the rate of new COVID-19 cases (i.e. n_covid19_r in the covid19 data frame) at multiple time points.

    @@ -708,19 +720,19 @@

    As for spatial data, dependence can also exists in temporal data. Temporal dependence or temporal autocorrelation exists when a variable’s value at time \(t\) is dependent on its value(s) at \(t-1\). More recent observations are often expected to have a greater influence on present observations. A key difference between temporal and spatial dependence is that temporal dependence is unidirectional (i.e. past observations can only affect present or future observations but not inversely), while spatial dependence is multidirectional (i.e. an observation in a spatial unit can influence and be influenced by observations in multiple spatial units).

    Before measuring the temporal dependence is our time-series, a time-series object needs to be created with a time stamp and given cycle frequency. A cycle frequency refers to when a seasonal pattern is repeated. We consider a time series of the total number of new COVID-19 cases per 100,000 (i.e. we sum cases over UTLAs by day) and the frequency set to 7 to reflect weekly cycles. So we end up with a data frame of length 71.

    -
    # create a time series object
    -total_cnt <- covid19 %>% group_by(date) %>%
    -  summarise(new_cases = sum(n_covid19_r)) 
    -total_cases_ts <- ts(total_cnt$new_cases, 
    -                     start = 1,
    -                     frequency =7)
    +
    # create a time series object
    +total_cnt <- covid19 %>% group_by(date) %>%
    +  summarise(new_cases = sum(n_covid19_r)) 
    +total_cases_ts <- ts(total_cnt$new_cases, 
    +                     start = 1,
    +                     frequency =7)

    There are various ways to test for temporal autocorrelation. An easy way is to compute the correlation coefficient between a time series measured at time \(t\) and its lag measured at time \(t-1\). Below we measure the temporal autocorrelation in the rate of new COVID-19 cases per 100,000 people. A correlation of 0.97 is returned indicating high positive autocorrelation; that is, high (low) past numbers of new COVID-19 cases per 100,000 people tend to correlate with higher (lower) present numbers of new COVID-19 cases. The Durbin-Watson test is often used to test for autocorrelation in regression models.

    -
    # create lag term t-1
    -lag_new_cases <- total_cnt$new_cases[-1]
    -total_cnt <- cbind(total_cnt[1:70,], lag_new_cases)
    -cor(total_cnt[,2:3])
    +
    # create lag term t-1
    +lag_new_cases <- total_cnt$new_cases[-1]
    +total_cnt <- cbind(total_cnt[1:70,], lag_new_cases)
    +cor(total_cnt[,2:3])
                  new_cases lag_new_cases
     new_cases      1.000000      0.974284
    @@ -736,20 +748,20 @@ 

    To understand and model a time series, these components need to be identified and appropriately incorporated into a regression model. We illustrate these components by decomposing our time series for total COVID-19 cases below. The top plot shows the observed data. Subsequent plots display the trend, seasonal and random components of the total number of COVID-19 cases on a weekly periodicity. They reveal a clear inverted U-shape trend and seasonal pattern. This idea that we can decompose data to extract information and understand temporal processes is key to understand the concept of basis functions to model spatio-temporal data, which will be introduced in the next section.

    -
    # decompose time series
    -dec_ts <- decompose(total_cases_ts)
    -# plot time series components
    -plot(dec_ts)
    +
    # decompose time series
    +dec_ts <- decompose(total_cases_ts)
    +# plot time series components
    +plot(dec_ts)
    -

    +
    +

    +
    +

    For a good introduction to time-series analysis in R, refer to Hyndman and Athanasopoulos (2018) and DataCamp.

    -

    - - -
    -

    10.6 Spatio-Temporal Data Modelling

    +

    +10.6 Spatio-Temporal Data Modelling

    Having some understanding of the spatio-temporal patterns of COVID-19 spread through data exploration, we are ready to start further examining structural relationships between the rate of new infections and local contextual factors via regression modelling across UTLAs. Specifically we consider the number of new cases per 100,000 people to capture the rate of new infections and only one contextual factor; that is, the share of population suffering from long-term sickness or disabled. We will consider some basic statistical models, of the form of linear regression and generalized linear models, to account for spatio-temporal dependencies in the data. Note that we do not consider more complex structures based on hierarchical models or spatio-temporal weighted regression models which would be the natural step moving forward.

    As any modelling approach, spatio-temporal statistical modelling has three principal goals:

      @@ -757,8 +769,8 @@

      -

      10.6.1 Intuition

      +

      +10.6.1 Intuition

      The key idea on what follows is to use a basic statistical regression model to understand the relationship between the share of new COVID-19 infections and the share of population suffering from long-term illness, accounting for spatio-temporal dependencies. We will consider what is known as a trend-surface regression model which assumes that spatio-temporal dependencies can be accounted for by “trend” components and incorporate as predictors in the model. Formally we consider the regression model below which seeks to account for spatial and temporal trends.

      \[y(s_{i}, t_{j}) = \beta_{0} + \beta_{k}x(s_{i}, t_{j}) + e(s_{i}, t_{j})\]

      where \(\beta_{0}\) is the intercept and \(\beta_{k}\) represents a set of regression coefficients associated with \(x(s_{i}, t_{j})\); the \(k\) indicates the number of covariates at spatial location \(s_{i}\) and time \(t_{j}\); \(e\) represents the regression errors which are assumed to follow a normal distribution. The key difference to aproaches considered in previous chapters is the incorporation of space and time together. As we learnt from the previous section, this has implications are we now have two sources of dependence: spatial and temporal autocorrelation, as well as seasonal and trend components. This has implications for modelling as we now need to account for all of these components if we are to establish any relationship between \(y\) and \(x\).

      @@ -782,42 +794,41 @@

      additional spatio-temporal basis functions which are presented below; and,

      These basis functions are incorporated as independent variables in the regression model. Additionally, we also include the share of population suffering from long-term illness as we know it is highly correlated to the cumulative number of COVID-19 cases. The share of population suffering long-term illness is incorporated as a spatial-variant, temporal-invariant covariates given that rely in 2011 census data.

      -

      -
      -

      10.6.2 Fitting Spatio-Temporal Models

      +

      +10.6.2 Fitting Spatio-Temporal Models

      As indicated at the start of this Chapter, we use the FRK framework developed by Cressie and Johannesson (2008). It provides a scalable, relies on the use a spatial random effects model (with which we have some familiarity) and can be easily implemented in R by the use of the FRK package (Zammit-Mangion and Cressie 2017). In this framework, a spatially correlated errors can be decomposed using a linear combination of spatial basis functions, effectively addressing issues of spatial-temporal dependence and nonstationarity. The specification of spatio-temporal basis functions is a key component of the model and they can be generated automatically or by the user via the FRK package. We will use the automatically generated functions. While as we will notice they are difficult to interpret, user generated functions require greater understanding of the spatio-temporal structure of COVID-19 which is beyond the scope of this Chapter.

      Prepare Data

      The first step to create a data frame with the variables that we will consider for the analysis. We first remove the geometries to convert covid19_spt from a simple feature object to a data frame and then compute the share of long-term illness population.

      -
      # remove geometries
      -st_geometry(covid19_spt) <- NULL
      -
      -# share of population in long-term illness 
      -covid19_spt <- covid19_spt %>% mutate(
      - lt_illness = Longterm_sick_or_disabled / Residents
      -)
      +
      # remove geometries
      +st_geometry(covid19_spt) <- NULL
      +
      +# share of population in long-term illness 
      +covid19_spt <- covid19_spt %>% mutate(
      + lt_illness = Longterm_sick_or_disabled / Residents
      +)

      Construct Basis Functions

      We now build the set of basis functions. The can be constructed by using the function auto_basis from the FRK package. The function takes as arguments: data, nres (which is the number of “resolutions” or aggregation to construct); and type of basis function to use. We consider a single resolution of the default Gaussian radial basis function.

      -
      # build basis functions
      -G <- auto_basis(data = covid19_spt[,c("long","lat")] %>%
      -                       SpatialPoints(),           # To sp obj
      -                nres = 1,                         # One resolution
      -                type = "Gaussian")                # Gaussian BFs
      -# basis functions evaluated at data locations are then the covariates
      -S <- eval_basis(basis = G,                       # basis functions
      -                s = covid19_spt[,c("long","lat")] %>%
      -                     as.matrix()) %>%            # conv. to matrix
      -     as.matrix()                                 # conv. to matrix
      -colnames(S) <- paste0("B", 1:ncol(S)) # assign column names
      +
      # build basis functions
      +G <- auto_basis(data = covid19_spt[,c("long","lat")] %>%
      +                       SpatialPoints(),           # To sp obj
      +                nres = 1,                         # One resolution
      +                type = "Gaussian")                # Gaussian BFs
      +# basis functions evaluated at data locations are then the covariates
      +S <- eval_basis(basis = G,                       # basis functions
      +                s = covid19_spt[,c("long","lat")] %>%
      +                     as.matrix()) %>%            # conv. to matrix
      +     as.matrix()                                 # conv. to matrix
      +colnames(S) <- paste0("B", 1:ncol(S)) # assign column names

      Add Basis Functions to Data Frame

      We then prepare a data frame for the regression model, adding the weights extracted from the basis functions. These weights enter as covariates in our model. Note that the resulting number of basis functions is nine. Explore by executing colnames(S). Below we select only relevant variables for our model.

      -
      # selecting variables
      -reg_df <- cbind(covid19_spt, S) %>%
      -  dplyr::select(ctyu19nm, n_covid19_r, long, lat, date, lt_illness, B1:B9)
      +
      # selecting variables
      +reg_df <- cbind(covid19_spt, S) %>%
      +  dplyr::select(ctyu19nm, n_covid19_r, long, lat, date, lt_illness, B1:B9)

      Fit Linear Regression

      We now fit a linear model using lm including as covariates longitude, latitude, day, share of long-term ill population and the nine basis functions.

      @@ -825,10 +836,10 @@

      -
      eq1 <- n_covid19_r ~ long + lat + date + lt_illness + .
      -lm_m <- lm(formula = eq1, 
      -           data = dplyr::select(reg_df, -ctyu19nm))
      -lm_m %>% summary()
      +
      eq1 <- n_covid19_r ~ long + lat + date + lt_illness + .
      +lm_m <- lm(formula = eq1, 
      +           data = dplyr::select(reg_df, -ctyu19nm))
      +lm_m %>% summary()
      
       Call:
      @@ -870,21 +881,17 @@ 

      -
      # estimate a poisson model
      -poisson_m1 <- glm(eq1,
      -                family = poisson("log"), # Poisson + log link
      -                data = dplyr::select(reg_df, -ctyu19nm))
      -poisson_m1 %>% summary()
      +
      # estimate a poisson model
      +poisson_m1 <- glm(eq1,
      +                family = poisson("log"), # Poisson + log link
      +                data = dplyr::select(reg_df, -ctyu19nm))
      +poisson_m1 %>% summary()
      
       Call:
       glm(formula = eq1, family = poisson("log"), data = dplyr::select(reg_df, 
           -ctyu19nm))
       
      -Deviance Residuals: 
      -    Min       1Q   Median       3Q      Max  
      --5.7454  -1.2043  -0.6993   0.3764   7.8981  
      -
       Coefficients:
                     Estimate Std. Error  z value Pr(>|z|)    
       (Intercept) -1.027e+03  8.168e+00 -125.699  < 2e-16 ***
      @@ -915,7 +922,7 @@ 

      -
      poisson_m1$deviance / poisson_m1$df.residual
      +
      poisson_m1$deviance / poisson_m1$df.residual
      [1] 2.29953
      @@ -924,21 +931,17 @@

      -
      # estimate a quasipoisson model
      -qpoisson_m1 <- glm(eq1,
      -                family = quasipoisson("log"), # QuasiPoisson + log link
      -                data = dplyr::select(reg_df, -ctyu19nm))
      -qpoisson_m1 %>% summary()
      +
      # estimate a quasipoisson model
      +qpoisson_m1 <- glm(eq1,
      +                family = quasipoisson("log"), # QuasiPoisson + log link
      +                data = dplyr::select(reg_df, -ctyu19nm))
      +qpoisson_m1 %>% summary()
      
       Call:
       glm(formula = eq1, family = quasipoisson("log"), data = dplyr::select(reg_df, 
           -ctyu19nm))
       
      -Deviance Residuals: 
      -    Min       1Q   Median       3Q      Max  
      --5.7454  -1.2043  -0.6993   0.3764   7.8981  
      -
       Coefficients:
                     Estimate Std. Error t value Pr(>|t|)    
       (Intercept) -1.027e+03  1.215e+01 -84.490  < 2e-16 ***
      @@ -970,20 +973,16 @@ 

      -
      # estimate a negative binomial model
      -nb_m1 <- glm.nb(eq1, 
      -       data = dplyr::select(reg_df, -ctyu19nm))
      -nb_m1 %>% summary()
      +
      # estimate a negative binomial model
      +nb_m1 <- glm.nb(eq1, 
      +       data = dplyr::select(reg_df, -ctyu19nm))
      +nb_m1 %>% summary()
      
       Call:
       glm.nb(formula = eq1, data = dplyr::select(reg_df, -ctyu19nm), 
           init.theta = 1.493089258, link = log)
       
      -Deviance Residuals: 
      -    Min       1Q   Median       3Q      Max  
      --3.0655  -0.8110  -0.4119   0.1861   3.1676  
      -
       Coefficients:
                     Estimate Std. Error z value Pr(>|z|)    
       (Intercept) -1.540e+03  1.596e+01 -96.459  < 2e-16 ***
      @@ -1021,22 +1020,18 @@ 

      -
      # new model specification
      -eq2 <- n_covid19_r ~ (long + lat + date)^2 + lt_illness + .
      -# estimate a negative binomial model
      -nb_m2 <- glm.nb(eq2, 
      -       data = dplyr::select(reg_df, -ctyu19nm))
      -nb_m2 %>% summary()
      +
      # new model specification
      +eq2 <- n_covid19_r ~ (long + lat + date)^2 + lt_illness + .
      +# estimate a negative binomial model
      +nb_m2 <- glm.nb(eq2, 
      +       data = dplyr::select(reg_df, -ctyu19nm))
      +nb_m2 %>% summary()
      
       Call:
       glm.nb(formula = eq2, data = dplyr::select(reg_df, -ctyu19nm), 
           init.theta = 1.56089592, link = log)
       
      -Deviance Residuals: 
      -    Min       1Q   Median       3Q      Max  
      --3.1506  -0.8099  -0.4039   0.2036   3.4084  
      -
       Coefficients:
                     Estimate Std. Error z value Pr(>|z|)    
       (Intercept)  4.797e+03  6.955e+02   6.897 5.32e-12 ***
      @@ -1076,56 +1071,59 @@ 

      -
      # export_summs(lm_m, poisson_m, qpoisson_m1, nb_m1, nb_m2)
      +
      # export_summs(lm_m, poisson_m, qpoisson_m1, nb_m1, nb_m2)

      Note that you may need to install the huxtable package.

      -
      -

      10.6.2.1 Model Comparision

      -

      To compare regression models based on different specifications and assumptions, like those reported above, you may want to consider the cross-validation approach used in Chapter 5. An alternative approach if you would like to get a quick sense of model fit is to explore the correlation between observed and predicted values of the dependent variable. For our models, we can achieve this by executing:

      +

      +10.6.2.1 Model Comparision

      +

      To compare regression models based on different specifications and assumptions, like those reported above, you may want to consider the cross-validation approach used in Chapter 5. An alternative approach if you would like to get a quick sense of model fit is to explore the correlation between observed and predicted values of the dependent variable. For our models, we can achieve this by executing:

      -
      # computing predictions for all models
      -lm_cnt <- predict(lm_m)
      -poisson_cnt <- predict(poisson_m1)
      -nb1_cnt <- predict(nb_m1)
      -nb2_cnt <- predict(nb_m2)
      -reg_df <- cbind(reg_df, lm_cnt, poisson_cnt, nb1_cnt, nb2_cnt)
      -
      -# computing correlation coefficients
      -cormat <- cor(reg_df[, c("n_covid19_r", "lm_cnt", "poisson_cnt", "nb1_cnt", "nb2_cnt")], 
      -              use="complete.obs", 
      -              method="pearson")
      -
      -# significance test
      -sig1 <- corrplot::cor.mtest(reg_df[, c("n_covid19_r", "lm_cnt", "poisson_cnt", "nb1_cnt", "nb2_cnt")],
      -                            conf.level = .95)
      -
      -# create a correlogram
      -corrplot::corrplot.mixed(cormat,
      -                         number.cex = 1,
      -                         tl.pos = "d",
      -                         tl.cex = 0.9)
      +
      # computing predictions for all models
      +lm_cnt <- predict(lm_m)
      +poisson_cnt <- predict(poisson_m1)
      +nb1_cnt <- predict(nb_m1)
      +nb2_cnt <- predict(nb_m2)
      +reg_df <- cbind(reg_df, lm_cnt, poisson_cnt, nb1_cnt, nb2_cnt)
      +
      +# computing correlation coefficients
      +cormat <- cor(reg_df[, c("n_covid19_r", "lm_cnt", "poisson_cnt", "nb1_cnt", "nb2_cnt")], 
      +              use="complete.obs", 
      +              method="pearson")
      +
      +# significance test
      +sig1 <- corrplot::cor.mtest(reg_df[, c("n_covid19_r", "lm_cnt", "poisson_cnt", "nb1_cnt", "nb2_cnt")],
      +                            conf.level = .95)
      +
      +# create a correlogram
      +corrplot::corrplot.mixed(cormat,
      +                         number.cex = 1,
      +                         tl.pos = "d",
      +                         tl.cex = 0.9)
      -

      +
      +

      +
      +

      All the models do a relatively job at predicting the observed count of new COVID-19 cases. They display correlation coefficients of 0.64/5 and high degree of correlation between them. These models will provide a good starting point for the assignment. There are a potentially few easy ways to make some considerable improvement.

        -
      • Option 1 Remove all zeros from the dependent variable n_covid19_r. They are likely to be affecting the ability of the model to predict positive values which are of main interest if we want to understand the spatio-temporal patterns of the outbreak.
      • -
      • Option 2 Remove all zeros from the dependent variable and additionally use its log for the regression model.
      • -
      • Option 3 Include more explanatory variables. Look for factors which can explain the spatial-temporal variability of the current COVID-19 outbreak. Look for hypotheses / anecdotal evidence from the newspapers and social media.
      • -
      • Option 4 Check for collinearity. Collinearity is likely to be an issue given the way basis functions are created. Checking for collinearity of course will not improve the fit of the existing model but it is important to remove collinear terms if statistical inference is a key goal - which in this case is. Over to you now!
      • -
      -
      -
      -

      -
      -

      10.7 Questions

      -

      We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

      +
    1. +Option 1 Remove all zeros from the dependent variable n_covid19_r. They are likely to be affecting the ability of the model to predict positive values which are of main interest if we want to understand the spatio-temporal patterns of the outbreak.
    2. +
    3. +Option 2 Remove all zeros from the dependent variable and additionally use its log for the regression model.
    4. +
    5. +Option 3 Include more explanatory variables. Look for factors which can explain the spatial-temporal variability of the current COVID-19 outbreak. Look for hypotheses / anecdotal evidence from the newspapers and social media.
    6. +
    7. +Option 4 Check for collinearity. Collinearity is likely to be an issue given the way basis functions are created. Checking for collinearity of course will not improve the fit of the existing model but it is important to remove collinear terms if statistical inference is a key goal - which in this case is. Over to you now!
    8. +

    +10.7 Questions

    +

    We will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.

    -
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    +
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    Reading layer `covid19_eng' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' 
       using driver `GPKG'
     Simple feature collection with 149 features and 507 fields
     Geometry type: MULTIPOLYGON
    @@ -1146,39 +1144,32 @@ 

    -
    -
    -
      -
    1. Read the file in R by executing read_tsv("data/sta/readme.txt"). Ensure the library readr is installed before running read_tsv.↩︎

    2. -
    -
    - - - -
    + \ No newline at end of file diff --git a/docs/10-st_analysis_files/figure-html/unnamed-chunk-9-1.png b/docs/10-st_analysis_files/figure-html/unnamed-chunk-9-1.png index 376bd92..864c1e2 100644 Binary files a/docs/10-st_analysis_files/figure-html/unnamed-chunk-9-1.png and b/docs/10-st_analysis_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/docs/11-datasets.html b/docs/11-datasets.html index 91e905e..a4e9d7c 100644 --- a/docs/11-datasets.html +++ b/docs/11-datasets.html @@ -1,12 +1,9 @@ - + - - + - - Spatial Modelling for Data Scientists - 11  Data Sets - @@ -103,14 +70,19 @@ - - - - - +} -
    -
    - -
    - -
    +
    -
    -
    - -
    -
    -

    11  Data Sets

    +
    +

    11  Data Sets

    @@ -283,25 +266,23 @@

    -
    library(sf)
    +

    -
    -

    11.1 Madrid AirBnb

    +

    +11.1 Madrid AirBnb

    This dataset contains a pre-processed set of properties advertised on the AirBnb website within the region of Madrid (Spain), together with house characteristics.

    -
    -

    Availability

    +

    Availability

    The dataset is stored on a Geopackage that can be found, within the structure of this project, under:

    -
    path <- "data/assignment_1_madrid/madrid_abb.gpkg"
    +
    path <- "data/assignment_1_madrid/madrid_abb.gpkg"
    -
    db <- st_read(path)
    +
    db <- st_read(path)
    Reading layer `madrid_abb' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' 
       using driver `GPKG'
     Simple feature collection with 18399 features and 16 fields
     Geometry type: POINT
    @@ -310,34 +291,45 @@ 

    Availability

    Geodetic CRS: WGS 84
    -
    -
    -

    Variables

    +

    Variables

    For each of the 17 properties, the following characteristics are available:

      -
    • price: [string] Price with currency
    • -
    • price_usd: [int] Price expressed in USD
    • -
    • log1pm_price_usd: [float] Log of the price
    • -
    • accommodates: [integer] Number of people the property accommodates
    • -
    • bathrooms: [integer] Number of bathrooms
    • -
    • bedrooms: [integer] Number of bedrooms
    • -
    • beds: [integer] Number of beds
    • -
    • neighbourhood: [string] Name of the neighbourhood the property is located in
    • -
    • room_type: [string] Type of room offered (shared, private, entire home, hotel room)
    • -
    • property_type: [string] Type of property advertised (apartment, house, hut, etc.)
    • -
    • WiFi: [binary] Takes 1 if the property has WiFi, 0 otherwise
    • -
    • Coffee: [binary] Takes 1 if the property has a coffee maker, 0 otherwise
    • -
    • Gym: [binary] Takes 1 if the property has access to a gym, 0 otherwise
    • -
    • Parking: [binary] Takes 1 if the property offers parking, 0 otherwise
    • -
    • km_to_retiro: [float] Euclidean distance from the property to the El Retiro park
    • -
    • geom: [geometry] Point geometry
    • -
    -
    -
    -

    Projection

    +
  • +price: [string] Price with currency
  • +
  • +price_usd: [int] Price expressed in USD
  • +
  • +log1pm_price_usd: [float] Log of the price
  • +
  • +accommodates: [integer] Number of people the property accommodates
  • +
  • +bathrooms: [integer] Number of bathrooms
  • +
  • +bedrooms: [integer] Number of bedrooms
  • +
  • +beds: [integer] Number of beds
  • +
  • +neighbourhood: [string] Name of the neighbourhood the property is located in
  • +
  • +room_type: [string] Type of room offered (shared, private, entire home, hotel room)
  • +
  • +property_type: [string] Type of property advertised (apartment, house, hut, etc.)
  • +
  • +WiFi: [binary] Takes 1 if the property has WiFi, 0 otherwise
  • +
  • +Coffee: [binary] Takes 1 if the property has a coffee maker, 0 otherwise
  • +
  • +Gym: [binary] Takes 1 if the property has access to a gym, 0 otherwise
  • +
  • +Parking: [binary] Takes 1 if the property offers parking, 0 otherwise
  • +
  • +km_to_retiro: [float] Euclidean distance from the property to the El Retiro park
  • +
  • +geom: [geometry] Point geometry
  • +

    Projection

    The location of each property is stored as point geometries and expressed in longitude and latitude coordinates:

    -
    st_crs(db)
    +
    st_crs(db)
    Coordinate Reference System:
       User input: WGS 84 
    @@ -370,14 +362,10 @@ 

    Projection

    ID["EPSG",4326]]
    -
    -
    -

    Source & Pre-processing

    +

    Source & Pre-processing

    The data are sourced from Inside Airbnb. A Jupyter notebook in Python (available at data/assignment_1_madrid/clean_data.ipynb) details the process from the original file available from source to the data in madrid_abb.gpkg.

    -
    -
    -
    -

    11.2 England COVID-19

    +

    +11.2 England COVID-19

    This dataset contains:

    • daily COVID-19 confirmed cases from 1st January, 2020 to 2nd February, 2021 from the GOV.UK dashboard;

    • @@ -385,14 +373,13 @@

      2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government.

    The data are at the Upper Tier Local Authority District (UTLAD) level - also known as Counties and Unitary Authorities.

    -
    -

    Availability

    +

    Availability

    The dataset is stored on a Geopackage:

    -
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    +
    sdf <- st_read("data/assignment_2_covid/covid19_eng.gpkg")
    Reading layer `covid19_eng' from data source 
    -  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' 
    +  `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' 
       using driver `GPKG'
     Simple feature collection with 149 features and 507 fields
     Geometry type: MULTIPOLYGON
    @@ -401,33 +388,44 @@ 

    Availability

    -
    -
    -

    Variables

    +

    Variables

    The data set contains 508 variables:

      -
    • objectid: [integer] unit identifier
    • -
    • ctyua19cd: [integer] Upper Tier Local Authority District (or Counties and Unitary Authorities) identifier
    • -
    • ctyua19nm: [character] Upper Tier Local Authority District (or Counties and Unitary Authorities) name
    • -
    • Region: [character] Region name
    • -
    • long: [numeric] longitude
    • -
    • lat: [numeric] latitude
    • -
    • st_areasha: [numeric] area in hectare
    • -
    • X2020.01.31 to X2021.02.05: [numeric] Daily COVID-19 cases from 31st January, 2020 to 5th February, 2021
    • -
    • IMD...Average.score - IMD.2019...Local.concentration: [numeric] IMD indicators - for details see File 11: upper-tier local authority summaries.
    • -
    • Residents: [numeric] Total resident population
    • -
    • Households: [numeric] Total households
    • -
    • Dwellings: [numeric] Total dwellings
    • -
    • Household_Spaces: [numeric] Total household spaces
    • -
    • Aged_16plus to Other_industry: [numeric] comprise 114 variables relating to various population and household attributes of the resident population. A description of all these variables can be found here
    • -
    • geom: [geometry] Point geometry
    • -
    -
    -
    -

    Projection

    +
  • +objectid: [integer] unit identifier
  • +
  • +ctyua19cd: [integer] Upper Tier Local Authority District (or Counties and Unitary Authorities) identifier
  • +
  • +ctyua19nm: [character] Upper Tier Local Authority District (or Counties and Unitary Authorities) name
  • +
  • +Region: [character] Region name
  • +
  • +long: [numeric] longitude
  • +
  • +lat: [numeric] latitude
  • +
  • +st_areasha: [numeric] area in hectare
  • +
  • +X2020.01.31 to X2021.02.05: [numeric] Daily COVID-19 cases from 31st January, 2020 to 5th February, 2021
  • +
  • +IMD...Average.score - IMD.2019...Local.concentration: [numeric] IMD indicators - for details see File 11: upper-tier local authority summaries.
  • +
  • +Residents: [numeric] Total resident population
  • +
  • +Households: [numeric] Total households
  • +
  • +Dwellings: [numeric] Total dwellings
  • +
  • +Household_Spaces: [numeric] Total household spaces
  • +
  • +Aged_16plus to Other_industry: [numeric] comprise 114 variables relating to various population and household attributes of the resident population. A description of all these variables can be found here +
  • +
  • +geom: [geometry] Point geometry
  • +

    Projection

    Details of the coordinate reference system:

    -
    st_crs(sdf)
    +
    st_crs(sdf)
    Coordinate Reference System:
       User input: OSGB36 / British National Grid 
    @@ -474,11 +472,9 @@ 

    Projection

    -
    -
    + -
    - -
    + \ No newline at end of file diff --git a/docs/Spatial-Modelling-for-Data-Scientists.pdf b/docs/Spatial-Modelling-for-Data-Scientists.pdf deleted file mode 100644 index 97101ea..0000000 Binary files a/docs/Spatial-Modelling-for-Data-Scientists.pdf and /dev/null differ diff --git a/docs/index.html b/docs/index.html index 49f5153..fa92063 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,12 +2,12 @@ - + - + Spatial Modelling for Data Scientists @@ -48,7 +82,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -57,24 +97,31 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } + -
    - @@ -82,105 +129,121 @@

    Spatial Modelling for Data Scientists

    -
    -

    Spatial Modelling for Data Scientists

    +

    Spatial Modelling for Data Scientists

    @@ -216,7 +279,7 @@

    Spatial Modelling for Data Scientists

    Published
    -

    March 13, 2023

    +

    February 1, 2024

    @@ -224,11 +287,13 @@

    Spatial Modelling for Data Scientists

    + +

    Welcome

    -

    This is the website for Spatial Modeling for Data Scientists 2022/23. This is a course taught by Professor Francisco Rowe at the University of Liverpool, United Kingdom. The course materials were developed by Professor Francisco Rowe and Professor Dani Arribas-Bel. You will learn how to analyse and model different types of spatial data as well as gaining an understanding of the various challenges arising from manipulating such data.

    +

    This is the website for Spatial Modeling for Data Scientists 2023/24. This is a course taught by Professor Francisco Rowe at the University of Liverpool, United Kingdom. The course materials were developed by Professor Francisco Rowe and Professor Dani Arribas-Bel. You will learn how to analyse and model different types of spatial data as well as gaining an understanding of the various challenges arising from manipulating such data.

    The website is licensed under the Attribution-NonCommercial-NoDerivatives 4.0 International License. A compilation of this web course is hosted as a GitHub repository that you can access:

    • As a download of a .zip file that contains all the materials.
    • @@ -248,6 +313,8 @@

      Contact

      + +
    @@ -279,9 +346,23 @@

    Contact

    icon: icon }; anchorJS.add('.anchored'); + const isCodeAnnotation = (el) => { + for (const clz of el.classList) { + if (clz.startsWith('code-annotation-')) { + return true; + } + } + return false; + } const clipboard = new window.ClipboardJS('.code-copy-button', { - target: function(trigger) { - return trigger.previousElementSibling; + text: function(trigger) { + const codeEl = trigger.previousElementSibling.cloneNode(true); + for (const childEl of codeEl.children) { + if (isCodeAnnotation(childEl)) { + childEl.remove(); + } + } + return codeEl.innerText; } }); clipboard.on('success', function(e) { @@ -317,10 +398,60 @@

    Contact

    // clear code selection e.clearSelection(); }); - function tippyHover(el, contentFn) { + const viewSource = window.document.getElementById('quarto-view-source') || + window.document.getElementById('quarto-code-tools-source'); + if (viewSource) { + const sourceUrl = viewSource.getAttribute("data-quarto-source-url"); + viewSource.addEventListener("click", function(e) { + if (sourceUrl) { + // rstudio viewer pane + if (/\bcapabilities=\b/.test(window.location)) { + window.open(sourceUrl); + } else { + window.location.href = sourceUrl; + } + } else { + const modal = new bootstrap.Modal(document.getElementById('quarto-embedded-source-code-modal')); + modal.show(); + } + return false; + }); + } + function toggleCodeHandler(show) { + return function(e) { + const detailsSrc = window.document.querySelectorAll(".cell > details > .sourceCode"); + for (let i=0; i .sourceCode"); + const fromCls = show ? "hidden" : "unhidden"; + const toCls = show ? "unhidden" : "hidden"; + for (let i=0; iContact interactive: true, interactiveBorder: 10, theme: 'quarto', - placement: 'bottom-start' + placement: 'bottom-start', }; + if (contentFn) { + config.content = contentFn; + } + if (onTriggerFn) { + config.onTrigger = onTriggerFn; + } + if (onUntriggerFn) { + config.onUntrigger = onUntriggerFn; + } window.tippy(el, config); } const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); @@ -346,6 +486,238 @@

    Contact

    return note.innerHTML; }); } + const xrefs = window.document.querySelectorAll('a.quarto-xref'); + const processXRef = (id, note) => { + // Strip column container classes + const stripColumnClz = (el) => { + el.classList.remove("page-full", "page-columns"); + if (el.children) { + for (const child of el.children) { + stripColumnClz(child); + } + } + } + stripColumnClz(note) + if (id === null || id.startsWith('sec-')) { + // Special case sections, only their first couple elements + const container = document.createElement("div"); + if (note.children && note.children.length > 2) { + container.appendChild(note.children[0].cloneNode(true)); + for (let i = 1; i < note.children.length; i++) { + const child = note.children[i]; + if (child.tagName === "P" && child.innerText === "") { + continue; + } else { + container.appendChild(child.cloneNode(true)); + break; + } + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(container); + } + return container.innerHTML + } else { + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + return note.innerHTML; + } + } else { + // Remove any anchor links if they are present + const anchorLink = note.querySelector('a.anchorjs-link'); + if (anchorLink) { + anchorLink.remove(); + } + if (window.Quarto?.typesetMath) { + window.Quarto.typesetMath(note); + } + // TODO in 1.5, we should make sure this works without a callout special case + if (note.classList.contains("callout")) { + return note.outerHTML; + } else { + return note.innerHTML; + } + } + } + for (var i=0; i res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.getElementById(id); + if (note !== null) { + const html = processXRef(id, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + } else { + // See if we can fetch a full url (with no hash to target) + // This is a special case and we should probably do some content thinning / targeting + fetch(url) + .then(res => res.text()) + .then(html => { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(html, "text/html"); + const note = htmlDoc.querySelector('main.content'); + if (note !== null) { + // This should only happen for chapter cross references + // (since there is no id in the URL) + // remove the first header + if (note.children.length > 0 && note.children[0].tagName === "HEADER") { + note.children[0].remove(); + } + const html = processXRef(null, note); + instance.setContent(html); + } + }).finally(() => { + instance.enable(); + instance.show(); + }); + } + }, function(instance) { + }); + } + let selectedAnnoteEl; + const selectorForAnnotation = ( cell, annotation) => { + let cellAttr = 'data-code-cell="' + cell + '"'; + let lineAttr = 'data-code-annotation="' + annotation + '"'; + const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; + return selector; + } + const selectCodeLines = (annoteEl) => { + const doc = window.document; + const targetCell = annoteEl.getAttribute("data-target-cell"); + const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); + const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); + const lines = annoteSpan.getAttribute("data-code-lines").split(","); + const lineIds = lines.map((line) => { + return targetCell + "-" + line; + }) + let top = null; + let height = null; + let parent = null; + if (lineIds.length > 0) { + //compute the position of the single el (top and bottom and make a div) + const el = window.document.getElementById(lineIds[0]); + top = el.offsetTop; + height = el.offsetHeight; + parent = el.parentElement.parentElement; + if (lineIds.length > 1) { + const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); + const bottom = lastEl.offsetTop + lastEl.offsetHeight; + height = bottom - top; + } + if (top !== null && height !== null && parent !== null) { + // cook up a div (if necessary) and position it + let div = window.document.getElementById("code-annotation-line-highlight"); + if (div === null) { + div = window.document.createElement("div"); + div.setAttribute("id", "code-annotation-line-highlight"); + div.style.position = 'absolute'; + parent.appendChild(div); + } + div.style.top = top - 2 + "px"; + div.style.height = height + 4 + "px"; + div.style.left = 0; + let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); + if (gutterDiv === null) { + gutterDiv = window.document.createElement("div"); + gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); + gutterDiv.style.position = 'absolute'; + const codeCell = window.document.getElementById(targetCell); + const gutter = codeCell.querySelector('.code-annotation-gutter'); + gutter.appendChild(gutterDiv); + } + gutterDiv.style.top = top - 2 + "px"; + gutterDiv.style.height = height + 4 + "px"; + } + selectedAnnoteEl = annoteEl; + } + }; + const unselectCodeLines = () => { + const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; + elementsIds.forEach((elId) => { + const div = window.document.getElementById(elId); + if (div) { + div.remove(); + } + }); + selectedAnnoteEl = undefined; + }; + // Handle positioning of the toggle + window.addEventListener( + "resize", + throttle(() => { + elRect = undefined; + if (selectedAnnoteEl) { + selectCodeLines(selectedAnnoteEl); + } + }, 10) + ); + function throttle(fn, ms) { + let throttle = false; + let timer; + return (...args) => { + if(!throttle) { // first call gets through + fn.apply(this, args); + throttle = true; + } else { // all the others get throttled + if(timer) clearTimeout(timer); // cancel #2 + timer = setTimeout(() => { + fn.apply(this, args); + timer = throttle = false; + }, ms); + } + }; + } + // Attach click handler to the DT + const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); + for (const annoteDlNode of annoteDls) { + annoteDlNode.addEventListener('click', (event) => { + const clickedEl = event.target; + if (clickedEl !== selectedAnnoteEl) { + unselectCodeLines(); + const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); + if (activeEl) { + activeEl.classList.remove('code-annotation-active'); + } + selectCodeLines(clickedEl); + clickedEl.classList.add('code-annotation-active'); + } else { + // Unselect the line + unselectCodeLines(); + clickedEl.classList.remove('code-annotation-active'); + } + }); + } const findCites = (el) => { const parentEl = el.parentElement; if (parentEl) { @@ -389,13 +761,35 @@

    Contact

    - +
    + + \ No newline at end of file diff --git a/docs/references.html b/docs/references.html index 5697bb5..14942ea 100644 --- a/docs/references.html +++ b/docs/references.html @@ -2,7 +2,7 @@ - + @@ -17,14 +17,49 @@ ul.task-list{list-style: none;} ul.task-list li input[type="checkbox"] { width: 0.8em; - margin: 0 0.8em 0.2em -1.6em; + margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ vertical-align: middle; } +/* CSS for syntax highlighting */ +pre > code.sourceCode { white-space: pre; position: relative; } +pre > code.sourceCode > span { line-height: 1.25; } +pre > code.sourceCode > span:empty { height: 1.2em; } +.sourceCode { overflow: visible; } +code.sourceCode > span { color: inherit; text-decoration: inherit; } +div.sourceCode { margin: 1em 0; } +pre.sourceCode { margin: 0; } +@media screen { +div.sourceCode { overflow: auto; } +} +@media print { +pre > code.sourceCode { white-space: pre-wrap; } +pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; } +} +pre.numberSource code + { counter-reset: source-line 0; } +pre.numberSource code > span + { position: relative; left: -4em; counter-increment: source-line; } +pre.numberSource code > span > a:first-child::before + { content: counter(source-line); + position: relative; left: -1em; text-align: right; vertical-align: baseline; + border: none; display: inline-block; + -webkit-touch-callout: none; -webkit-user-select: none; + -khtml-user-select: none; -moz-user-select: none; + -ms-user-select: none; user-select: none; + padding: 0 4px; width: 4em; + } +pre.numberSource { margin-left: 3em; padding-left: 4px; } +div.sourceCode + { } +@media screen { +pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; } +} +/* CSS for citations */ div.csl-bib-body { } div.csl-entry { clear: both; } -.hanging div.csl-entry { +.hanging-indent div.csl-entry { margin-left:2em; text-indent:-2em; } @@ -38,8 +73,7 @@ } div.csl-indent { margin-left: 2em; -} - +} @@ -65,7 +99,13 @@ "collapse-after": 3, "panel-placement": "start", "type": "textbox", - "limit": 20, + "limit": 50, + "keyboard-shortcut": [ + "f", + "/", + "s" + ], + "show-item-context": false, "language": { "search-no-results-text": "No results", "search-matching-documents-text": "matching documents", @@ -74,24 +114,31 @@ "search-more-match-text": "more match in this document", "search-more-matches-text": "more matches in this document", "search-clear-button-title": "Clear", + "search-text-placeholder": "", "search-detached-cancel-button-title": "Cancel", - "search-submit-button-title": "Submit" + "search-submit-button-title": "Submit", + "search-label": "Search" } } + -
    - @@ -99,105 +146,121 @@

    References

    -
    -
    -
    + +
    +
    Anselin, Luc. 1988. Spatial Econometrics: Methods and Models. Vol. 4. Springer Science & Business Media.
    -
    +
    ———. 2003. “Spatial Externalities, Spatial Multipliers, and Spatial Econometrics.” International Regional Science Review 26 (2): 153–66.
    -
    +
    ———. 2007. “Spatial Regression Analysis in r–a Workbook.” Center for Spatially Integrated Social Science. http://csiss.org/GISPopSci/workshops/2011/PSU/readings/W15_Anselin2007.pdf.
    -
    +
    Anselin, Luc, and Sergio J. Rey. 2014. Modern Spatial Econometrics in Practice: A Guide to GeoDa, GeoDaSpace and PySAL. GeoDa Press LLC.
    -
    +
    +Appelhans, Tim, Florian Detsch, Christoph Reudenbach, and Stefan +Woellauer. 2022. Mapview: Interactive Viewing of Spatial Data in +r. https://github.com/r-spatial/mapview. +
    +
    Arribas-Bel, Dani. 2014. “Spatial Data, Analysis, and Regression-a Mini Course.” REGION 1 (1): R1. http://darribas.org/sdar_mini.
    -
    +
    ———. 2019. “A Course on Geographic Data Science.” The Journal of Open Source Education 2 (14). https://doi.org/https://doi.org/10.21105/jose.00042.
    -
    +
    Arribas-Bel, Daniel, M.-À. Garcia-López, and Elisabet Viladecans-Marsal. 2021. “Building(s and) Cities: Delineating Urban Areas with a Machine Learning Algorithm.” Journal of Urban Economics 125 (September): 103217. https://doi.org/10.1016/j.jue.2019.103217.
    -
    +
    +Baddeley, Adrian, Ege Rubak, and Rolf Turner. 2015. Spatial Point +Patterns: Methodology and Applications with r. CRC press. +
    +
    +Baddeley, Adrian, Rolf Turner, and Ege Rubak. 2022. Spatstat: +Spatial Point Pattern Analysis, Model-Fitting, Simulation, Tests. +http://spatstat.org/. +
    +
    Banerjee, Sudipto, Bradley P Carlin, and Alan E Gelfand. 2014. Hierarchical Modeling and Analysis for Spatial Data. Crc Press.
    -
    +
    Belsley, David A, Edwin Kuh, and Roy E Welsch. 2005. Regression Diagnostics: Identifying Influential Data and Sources of Collinearity. Vol. 571. John Wiley & Sons.
    -
    +
    +Bivand, Roger. 2022. Spdep: Spatial Dependence: Weighting Schemes, +Statistics. +
    +
    Bivand, Roger S., Edzer Pebesma, and Virgilio Gómez-Rubio. 2013. Applied Spatial Data Analysis with r. Springer New York. https://doi.org/10.1007/978-1-4614-7618-4.
    -
    +
    +Bivand, Roger, and Gianfranco Piras. 2022. Spatialreg: Spatial +Regression Analysis. https://CRAN.R-project.org/package=spatialreg. +
    +
    Brunsdon, Chris, and Lex Comber. 2015. An Introduction to r for Spatial Analysis & Mapping. Sage.
    -
    +
    Brunsdon, Chris, Stewart Fotheringham, and Martin Charlton. 1998. “Geographically Weighted Regression.” Journal of the Royal Statistical Society: Series D (The Statistician) 47 (3): 431–43.
    -
    +
    Casado-Díaz, José Manuel, Lucas Martínez-Bernabéu, and Francisco Rowe. 2017. “An Evolutionary Approach to the Delimitation of Labour Market Areas: An Empirical Application for Chile.” Spatial Economic Analysis 12 (4): 379–403. https://doi.org/10.1080/17421772.2017.1273541.
    -
    +
    Comber, Alexis, Christopher Brunsdon, Martin Charlton, Guanpeng Dong, Richard Harris, Binbin Lu, Yihe Lü, et al. 2022. “A Route Map for Successful Applications of Geographically Weighted Regression.” Geographical Analysis 55 (1): 155–78. https://doi.org/10.1111/gean.12316.
    -
    +
    Cressie, Noel. 2015. Statistics for Spatial Data. John Wiley & Sons.
    -
    +
    Cressie, Noel, and Gardar Johannesson. 2008. “Fixed Rank Kriging for Very Large Spatial Data Sets.” Journal of the Royal Statistical Society: Series B (Statistical Methodology) 70 (1): 209–26.
    -
    +
    +Dunnington, Dewey, Edzer Pebesma, and Ege Rubak. 2023. S2: Spherical +Geometry Operators Using the S2 Geometry Library. https://CRAN.R-project.org/package=s2. +
    +
    Fotheringham, A Stewart, and Morton E O’Kelly. 1989. Spatial Interaction Models: Formulations and Applications. Vol. 1. Kluwer Academic Publishers Dordrecht.
    -
    +
    Fotheringham, A Stewart, and David WS Wong. 1991. “The Modifiable Areal Unit Problem in Multivariate Statistical Analysis.” Environment and Planning A 23 (7): 1025–44.
    -
    +
    Fotheringham, Stewart, Chris Brunsdon, and Martin Charlton. 2002. Geographically Weighted Regression. John Wiley & Sons.
    -
    +
    Gabadinho, Alexis, Gilbert Ritschard, Matthias Studer, and Nicolas S Müller. 2009. “Mining Sequence Data in r with the TraMineR Package: A User’s Guide.” Geneva: Department of Econometrics and Laboratory of Demography, University of Geneva.
    -
    +
    Gelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.
    -
    +
    Gibbons, Stephen, Henry G Overman, and Eleonora Patacchini. 2014. “Spatial Methods.”
    -
    +
    Grolemund, Garrett, and Hadley Wickham. 2019. R for Data Science. O’Reilly, US. https://r4ds.had.co.nz.
    -
    +
    Hyndman, Rob J, and George Athanasopoulos. 2018. Forecasting: Principles and Practice. OTexts.
    -
    +
    Kong, Xiangjie, Menglin Li, Kai Ma, Kaiqi Tian, Mengyuan Wang, Zhaolong Ning, and Feng Xia. 2018. “Big Trajectory Data: A Survey of Applications and Services.” IEEE Access 6: 58295–306. https://doi.org/10.1109/access.2018.2873779.
    -
    +
    Kwan, Mei-Po, and Jiyeong Lee. 2004. “Geovisualization of Human Activity Patterns Using 3D GIS: A Time-Geographic Approach.” Spatially Integrated Social Science 27: 721–44.
    -
    +
    Loidl, Martin, Gudrun Wallentin, Robin Wendel, and Bernhard Zagel. 2016. “Mapping Bicycle Crash Risk Patterns on the Local Scale.” Safety 2 (3): 17. https://doi.org/10.3390/safety2030017.
    -
    +
    Lovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.
    -
    +
    +———. 2024. Geocomputation with r. Online. https://doi.org/10.1201/9780203730058. +
    +
    Lu, Binbin, Paul Harris, Martin Charlton, and Chris Brunsdon. 2014. “The GWmodel r Package: Further Topics for Exploring Spatial Heterogeneity Using Geographically Weighted Models.” Geo-Spatial Information Science 17 (2): 85–101.
    -
    +
    Multilevel Modelling, Centre for. n.d. “Introduction to Multilevel Modelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04.
    -
    +
    Niedomysl, Thomas. 2011. “How Migration Motives Change over Migration Distance: Evidence on Variation Across Socio-Economic and Demographic Groups.” Regional Studies 45 (6): 843–55. https://doi.org/10.1080/00343401003614266.
    -
    +
    Önnerfors, Åsa, Mariana Kotzeva, Teodóra Brandmüller, et al. 2019. “Eurostat Regional Yearbook 2019 Edition.”
    -
    +
    Openshaw, Stan. 1981. “The Modifiable Areal Unit Problem.” Quantitative Geography: A British View, 60–69.
    -
    +
    Patias, Nikos, Francisco Rowe, and Dani Arribas-Bel. 2021. “Trajectories of Neighbourhood Inequality in Britain: Unpacking Inter-Regional Socioeconomic Imbalances, 1971-2011.” The Geographical Journal 188 (2): 150–65. https://doi.org/10.1111/geoj.12420.
    -
    +
    Patias, Nikos, Francisco Rowe, and Stefano Cavazzi. 2019. “A Scalable Analytical Framework for Spatio-Temporal Analysis of Neighborhood Change: A Sequence Analysis Approach.” In, 223–41. Springer International Publishing. https://doi.org/10.1007/978-3-030-14745-7_13.
    -
    +
    +Pebesma, Edzer. 2004. “Multivariable Geostatistics in S: The Gstat +Package.” Computers & Geosciences 30 (7): 683–91. https://doi.org/10.1016/j.cageo.2004.03.012. +
    +
    Pebesma, Edzer et al. 2012. “Spacetime: Spatio-Temporal Data in r.” Journal of Statistical Software 51 (7): 1–30.
    -
    -Rey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. forthcoming. +
    +Pebesma, Edzer. 2018. Simple Features for R: +Standardized Support for Spatial Vector Data.” +The R Journal 10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009. +
    +
    +———. 2022a. Sf: Simple Features for r. https://CRAN.R-project.org/package=sf. +
    +
    +———. 2022b. Stars: Spatiotemporal Arrays, Raster and Vector Data +Cubes. https://CRAN.R-project.org/package=stars. +
    +
    +———. 2023. Lwgeom: Bindings to Selected Liblwgeom Functions for +Simple Features. https://github.com/r-spatial/lwgeom/. +
    +
    +Pebesma, Edzer, and Roger Bivand. 2023. Spatial Data Science: With +Applications in r. CRC Press. +
    +
    +Pebesma, Edzer, and Benedikt Graeler. 2022. Gstat: Spatial and +Spatio-Temporal Geostatistical Modelling, Prediction and +Simulation. https://github.com/r-spatial/gstat/. +
    +
    +Rey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. 2023. Geographic Data Science with PySAL and the PyData Stack. CRC press.
    -
    +
    Robinson, WS. 1950. “Ecological Correlations and Individual Behavior.” American Sociological Review 15 (195): 351–57.
    -
    +
    Rowe, Francisco. 2022. “Introduction to Geographic Data Science.” Open Science Framework, August. https://doi.org/10.17605/OSF.IO/VHY2P.
    -
    +
    Rowe, Francisco, Robin Lovelace, and Adam Dennett. 2022. “Spatial Interaction Modelling: A Manifesto.” http://dx.doi.org/10.31219/osf.io/xcdms.
    -
    +
    Rowe, Francisco, and Nikos Patias. 2020. “Mapping the Spatial Patterns of Internal Migration in Europe.” Regional Studies, Regional Science 7 (1): 390–93. https://doi.org/10.1080/21681376.2020.1811139.
    -
    +
    Singleton, Alex. 2017. “Geographic Data Science for Urban Analytics.” http://www.alex-singleton.com/GDS_UA_2017/.
    -
    +
    Singleton, Alexander D., and Seth E. Spielman. 2013. “The Past, Present, and Future of Geodemographic Research in the United States and United Kingdom.” The Professional Geographer 66 (4): 558–67. https://doi.org/10.1080/00330124.2013.848764.
    -
    +
    Stillwell, John, Konstantinos Daras, and Martin Bell. 2018. “Spatial Aggregation Methods for Investigating the MAUP Effects in Migration Analysis.” Applied Spatial Analysis and Policy 11 (4): 693–711. https://doi.org/10.1007/s12061-018-9274-6.
    -
    +
    Tao, Sui, Jonathan Corcoran, Francisco Rowe, and Mark Hickman. 2018. “To Travel or Not to Travel: Weather Is the Question. Modelling the Effect of Local Weather Conditions on Bus Ridership.” Transportation Research Part C: Emerging Technologies 86 (January): 147–67. https://doi.org/10.1016/j.trc.2017.11.005.
    -
    +
    +Tennekes, Martijn. 2018. tmap: +Thematic Maps in R.” Journal of Statistical +Software 84 (6): 1–39. https://doi.org/10.18637/jss.v084.i06. +
    +
    +———. 2022. Tmap: Thematic Maps. https://github.com/r-tmap/tmap. +
    +
    Wheeler, David, and Michael Tiefelsdorf. 2005. “Multicollinearity and Correlation Among Local Regression Coefficients in Geographically Weighted Regression.” Journal of Geographical Systems 7 (2): 161–87.
    -
    +
    +Wickham, Hadley. 2009. Ggplot2. Springer New York. https://doi.org/10.1007/978-0-387-98141-3. +
    +
    +Wickham, Hadley, Mara Averick, Jennifer Bryan, Winston Chang, Lucy +D’Agostino McGowan, Romain François, Garrett Grolemund, et al. 2019. +“Welcome to the tidyverse.” +Journal of Open Source Software 4 (43): 1686. https://doi.org/10.21105/joss.01686. +
    +
    +Wickham, Hadley, Mine Çetinkaya-Rundel, and Garrett Grolemund. 2023. +R for Data Science. " O’Reilly Media, Inc.". +
    +
    Wikle, Christopher K, Andrew Zammit-Mangion, and Noel Cressie. 2019. Spatio-Temporal Statistics with r. CRC Press.
    -
    +
    Williamson, Paul. 2018. “Survey Analysis.”
    -
    +
    Wolf, Levi John, Sean Fox, Rich Harris, Ron Johnston, Kelvyn Jones, David Manley, Emmanouil Tranos, and Wenfei Winnie Wang. 2020. “Quantitative Geography III: Future Challenges and Challenging Futures.” Progress in Human Geography 45 (3): 596–608. https://doi.org/10.1177/0309132520924722.
    -
    +
    Xie, Yihui, JJ Allaire, and Garrett Grolemund. 2019. R Markdown: The Definitive Guide. CRC Press, Taylor & Francis, Chapman & Hall Book. https://bookdown.org/yihui/rmarkdown/.
    -
    +
    Zammit-Mangion, Andrew, and Noel Cressie. 2017. “FRK: An r Package for Spatial and Spatio-Temporal Prediction with Large Datasets.” arXiv Preprint arXiv:1705.08105. @@ -475,6 +619,8 @@

    References

    + + +
    + + \ No newline at end of file diff --git a/docs/search.json b/docs/search.json index 43000f8..f99821a 100644 --- a/docs/search.json +++ b/docs/search.json @@ -4,489 +4,729 @@ "href": "index.html", "title": "Spatial Modelling for Data Scientists", "section": "", - "text": "Welcome\nThis is the website for Spatial Modeling for Data Scientists 2022/23. This is a course taught by Professor Francisco Rowe at the University of Liverpool, United Kingdom. The course materials were developed by Professor Francisco Rowe and Professor Dani Arribas-Bel. You will learn how to analyse and model different types of spatial data as well as gaining an understanding of the various challenges arising from manipulating such data.\nThe website is licensed under the Attribution-NonCommercial-NoDerivatives 4.0 International License. A compilation of this web course is hosted as a GitHub repository that you can access:" + "text": "Welcome\nThis is the website for Spatial Modeling for Data Scientists 2023/24. This is a course taught by Professor Francisco Rowe at the University of Liverpool, United Kingdom. The course materials were developed by Professor Francisco Rowe and Professor Dani Arribas-Bel. You will learn how to analyse and model different types of spatial data as well as gaining an understanding of the various challenges arising from manipulating such data.\nThe website is licensed under the Attribution-NonCommercial-NoDerivatives 4.0 International License. A compilation of this web course is hosted as a GitHub repository that you can access:", + "crumbs": [ + "Welcome" + ] }, { "objectID": "index.html#contact", "href": "index.html#contact", "title": "Spatial Modelling for Data Scientists", "section": "Contact", - "text": "Contact\n\nFrancisco Rowe - F.Rowe-Gonzalez [at] liverpool.ac.uk\nProfessor of Population Data Science\nOffice 507, Roxby Building,\nUniversity of Liverpool - 74 Bedford St S,\nLiverpool, L69 7ZT,\nUnited Kingdom." + "text": "Contact\n\nFrancisco Rowe - F.Rowe-Gonzalez [at] liverpool.ac.uk\nProfessor of Population Data Science\nOffice 507, Roxby Building,\nUniversity of Liverpool - 74 Bedford St S,\nLiverpool, L69 7ZT,\nUnited Kingdom.", + "crumbs": [ + "Welcome" + ] + }, + { + "objectID": "01-overview.html", + "href": "01-overview.html", + "title": "1  Overview", + "section": "", + "text": "1.1 Aims\nThis module aims to provides students with a range of techniques for analysing and modelling spatial data:", + "crumbs": [ + "1  Overview" + ] }, { "objectID": "01-overview.html#aims", "href": "01-overview.html#aims", "title": "1  Overview", - "section": "1.1 Aims", - "text": "1.1 Aims\nThis module aims to provides students with a range of techniques for analysing and modelling spatial data:\n\nbuild upon the more general research training delivered via companion modules on Data Collection and Data Analysis, both of which have an aspatial focus;\nhighlight a number of key social issues that have a spatial dimension;\nexplain the specific challenges faced when attempting to analyse spatial data;\nintroduce a range of analytical techniques and approaches suitable for the analysis of spatial data; and,\nenhance practical skills in using R software packages to implement a wide range of spatial analytical tools." + "section": "", + "text": "build upon the more general research training delivered via companion modules on Data Collection and Data Analysis, both of which have an aspatial focus;\nhighlight a number of key social issues that have a spatial dimension;\nexplain the specific challenges faced when attempting to analyse spatial data;\nintroduce a range of analytical techniques and approaches suitable for the analysis of spatial data; and,\nenhance practical skills in using R software packages to implement a wide range of spatial analytical tools.", + "crumbs": [ + "1  Overview" + ] }, { "objectID": "01-overview.html#learning-outcomes", "href": "01-overview.html#learning-outcomes", "title": "1  Overview", - "section": "1.2 Learning Outcomes", - "text": "1.2 Learning Outcomes\nBy the end of the module, students should be able to:\n\nidentify some key sources of spatial data and resources of spatial analysis and modelling tools;\nexplain the advantages of taking spatial structure into account when analysing spatial data;\napply a range of computer-based techniques for the analysis of spatial data, including mapping, correlation, kernel density estimation, regression, multi-level models, geographically-weighted regression, spatial interaction models and spatial econometrics;\napply appropriate analytical strategies to tackle the key methodological challenges facing spatial analysis – spatial autocorrelation, heterogeneity, and ecological fallacy; and,\nselect appropriate analytical tools for analysing specific spatial data sets to address emerging social issues facing the society." + "section": "\n1.2 Learning Outcomes", + "text": "1.2 Learning Outcomes\nBy the end of the module, students should be able to:\n\nidentify some key sources of spatial data and resources of spatial analysis and modelling tools;\nexplain the advantages of taking spatial structure into account when analysing spatial data;\napply a range of computer-based techniques for the analysis of spatial data, including mapping, correlation, kernel density estimation, regression, multi-level models, geographically-weighted regression, spatial interaction models and spatial econometrics;\napply appropriate analytical strategies to tackle the key methodological challenges facing spatial analysis – spatial autocorrelation, heterogeneity, and ecological fallacy; and,\nselect appropriate analytical tools for analysing specific spatial data sets to address emerging social issues facing the society.", + "crumbs": [ + "1  Overview" + ] }, { "objectID": "01-overview.html#feedback", "href": "01-overview.html#feedback", "title": "1  Overview", - "section": "1.3 Feedback", - "text": "1.3 Feedback\n\nFormal assessment of two computational essays. Written assignment-specific feedback will be provided within three working weeks of the submission deadline. Comments will offer an understanding of the mark awarded and identify areas which can be considered for improvement in future assignments.\nVerbal face-to-face feedback. Immediate face-to-face feedback will be provided during lecture, discussion and clinic sessions in interaction with staff. This will take place in all live sessions during the semester.\nOnline forum. Asynchronous written feedback will be provided via an online forum maintained by the module lead. Students are encouraged to contribute by asking and answering questions relating to the module content. Staff will monitor the forum Monday to Friday 9am-5pm, but it will be open to students to make contributions at all times." + "section": "\n1.3 Feedback", + "text": "1.3 Feedback\n\nFormal assessment of two computational essays. Written assignment-specific feedback will be provided within three working weeks of the submission deadline. Comments will offer an understanding of the mark awarded and identify areas which can be considered for improvement in future assignments.\nVerbal face-to-face feedback. Immediate face-to-face feedback will be provided during lecture, discussion and clinic sessions in interaction with staff. This will take place in all live sessions during the semester.\nOnline forum. Asynchronous written feedback will be provided via an online forum maintained by the module lead. Students are encouraged to contribute by asking and answering questions relating to the module content. Staff will monitor the forum Monday to Friday 9am-5pm, but it will be open to students to make contributions at all times.", + "crumbs": [ + "1  Overview" + ] }, { "objectID": "01-overview.html#computational-environment", "href": "01-overview.html#computational-environment", "title": "1  Overview", - "section": "1.4 Computational Environment", - "text": "1.4 Computational Environment\nTo reproduce the code in the book, you need the following software packages:\n\nR-4.2.2\nRStudio 2022.12.0-353\nQuarto 1.2.280\nthe list of libraries in the next section\n\nTo check your version of:\n\nR and libraries run sessionInfo()\nRStudio click help on the menu bar and then About\nQuarto check the version file in the quarto folder on your computer.\n\nTo install and update:\n\nR, download the appropriate version from The Comprehensive R Archive Network (CRAN)\nRStudio, download the appropriate version from Posit\nQuarto, download the appropriate version from the Quarto website\n\n\n1.4.1 Dependency list\nThe list of libraries used in this book is provided below:\n\narm\ncar\ncorrplot\nFRK\ngghighlight\nggplot2\nggmap\nGISTools\ngridExtra\ngstat\nhexbin\njtools\nkableExtra\nknitr\nlme4\nlmtest\nlubridate\nMASS\nmerTools\nplyr\nRColorBrewer\nrgdal\nsf\nsjPlot\nsp\nspgwr\nspatialreg\nspacetime\nstargazer\ntidyverse\ntmap\ntufte\nviridis\n\nCopy, paste and run the code below in your console. Ensure all packages are installed on your computer.\n\ndeps <- list(\n \"arm\",\n \"car\",\n \"corrplot\",\n \"FRK\",\n \"gghighlight\",\n \"ggplot2\",\n \"ggmap\",\n \"gridExtra\",\n \"gstat\",\n \"hexbin\",\n \"jtools\",\n \"kableExtra\",\n \"knitr\",\n \"lme4\",\n \"lmtest\",\n \"lubridate\",\n \"MASS\",\n \"merTools\",\n \"plyr\",\n \"RColorBrewer\",\n \"rgdal\",\n \"sf\",\n \"sjPlot\",\n \"sp\",\n \"spgwr\",\n \"spatialreg\",\n \"spacetime\",\n \"stargazer\",\n \"tidyverse\",\n \"tmap\",\n \"tufte\",\n \"viridis\"\n)\n\n\n# we can load them all to make sure they are installed:\nfor(lib in deps){library(lib, character.only = TRUE)}" + "section": "\n1.4 Computational Environment", + "text": "1.4 Computational Environment\nTo reproduce the code in the book, you need the following software packages:\n\nR-4.3.1\nRStudio 2023.09.0+463\nQuarto 1.3.450\nthe list of libraries in the next section\n\nTo check your version of:\n\nR and libraries run sessionInfo()\n\nRStudio click help on the menu bar and then About\n\nQuarto check the version file in the quarto folder on your computer.\n\nTo install and update:\n\nR, download the appropriate version from The Comprehensive R Archive Network (CRAN)\n\nRStudio, download the appropriate version from Posit\n\nQuarto, download the appropriate version from the Quarto website\n\n\n\n1.4.1 Dependency list\nThe list of libraries used in this book is provided below:\n\narm\ncar\ncorrplot\ndevtools\nFRK\ngghighlight\nggplot2\nggmap\nGISTools\ngridExtra\ngstat\nhexbin\njtools\nkableExtra\nknitr\nlme4\nlmtest\nlubridate\nMASS\nmerTools\nplyr\nRColorBrewer\nrgdal\nsf\nsjPlot\nsp\nspgwr\nspatialreg\nspacetime\nstargazer\ntidyverse\ntmap\ntufte\nviridis\nbasemapR\n\nCopy, paste and run the code below in your console. Ensure all packages are installed on your computer.\n\n# package names\npackages <- c(\n \"arm\",\n \"car\",\n \"corrplot\",\n \"devtools\",\n \"FRK\",\n \"gghighlight\",\n \"ggplot2\",\n \"ggmap\",\n \"gridExtra\",\n \"gstat\",\n \"hexbin\",\n \"jtools\",\n \"kableExtra\",\n \"knitr\",\n \"lme4\",\n \"lmtest\",\n \"lubridate\",\n \"MASS\",\n \"merTools\",\n \"plyr\",\n \"RColorBrewer\",\n \"sf\",\n \"sjPlot\",\n \"sp\",\n \"spgwr\",\n \"spatialreg\",\n \"spacetime\",\n \"stargazer\",\n \"tidyverse\",\n \"tmap\",\n \"tufte\",\n \"viridis\"\n)\n\n# install packages not yet installed\ninstalled_packages <- packages %in% rownames(installed.packages())\nif (any(installed_packages == FALSE)) {\n install.packages(packages[!installed_packages])\n}\n\n# packages loading\ninvisible(lapply(packages, library, character.only = TRUE))\n\n::: column-margin ::: callout-note To install the library basemapR, you need to install from source by running:\nlibrary(devtools)install_github('Chrisjb/basemapR') ::: ::: column-margin", + "crumbs": [ + "1  Overview" + ] }, { "objectID": "01-overview.html#assessment", "href": "01-overview.html#assessment", "title": "1  Overview", - "section": "1.5 Assessment", - "text": "1.5 Assessment\nThe final module mark is composed of the two computational essays. Together they are designed to cover the materials introduced in the entirety of content covered during the semester. A computational essay is an essay whose narrative is supported by code and computational results that are included in the essay itself. Each teaching week, you will be required to address a set of questions relating to the module content covered in that week, and to use the material that you will produce for this purpose to build your computational essay.\nAssignment 1 (50%) refer to the set of questions at the end of Chapter 4, Chapter 5 and Chapter 6. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.\nAssignment 2 (50%) refer to the set of questions at the end of Chapter 7, Chapter 8, Chapter 9 and Chapter 10. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.\n\n1.5.1 Format Requirements\nBoth assignments will have the same requirements:\n\nMaximum word count: 2,000 words, excluding figures and references.\nUp to three maps, plot or figures (a figure may include more than one map and/or plot and will only count as one but needs to be integrated in the figure)\nUp to two tables.\n\nAssignments need to be prepared in “Quarto Document” format (i.e. qmd extension) and then converted into a self-contained HTML file that will then be submitted via Turnitin. The document should only display content that will be assessed. Intermediate steps do not need to be displayed. Messages resulting from loading packages, attaching data frames, or similar messages do not need to be included as output code. Useful resources to customise your R notebook can be found on Quarto’s website.\nTwo Quarto Document templates will be available via the module Canvas site.\nSubmission is electronic only via Turnitin on Canvas.\n\n\n1.5.2 Marking criteria\nThe Standard Environmental Sciences School marking criteria apply, with a stronger emphasis on evidencing the use of regression models, critical analysis of results and presentation standards. In addition to these general criteria, the code and outputs (i.e. tables, maps and plots) contained within the notebook submitted for assessment will be assessed according to the extent of documentation and evidence of expertise in changing and extending the code options illustrated in each chapter. Specifically, the following criteria will be applied:\n\n0-15: no documentation and use of default options.\n16-39: little documentation and use of default options.\n40-49: some documentation, and use of default options.\n50-59: extensive documentation, and edit of some of the options provided in the notebook (e.g. change north arrow location).\n60-69: extensive well organised and easy to read documentation, and evidence of understanding of options provided in the code (e.g. tweaking existing options).\n70-79: all above, plus clear evidence of code design skills (e.g. customising graphics, combining plots (or tables) into a single output, adding clear axis labels and variable names on graphic outputs, etc.).\n80-100: all as above, plus code containing novel contributions that extend/improve the functionality the code was provided with (e.g. comparative model assessments, novel methods to perform the task, etc.)." + "section": "\n1.5 Assessment", + "text": "1.5 Assessment\nThe final module mark is composed of the two computational essays. Together they are designed to cover the materials introduced in the entirety of content covered during the semester. A computational essay is an essay whose narrative is supported by code and computational results that are included in the essay itself. Each teaching week, you will be required to address a set of questions relating to the module content covered in that week, and to use the material that you will produce for this purpose to build your computational essay.\nAssignment 1 (50%) refer to the set of questions at the end of Chapter 4, Chapter 5 and Chapter 6. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.\nAssignment 2 (50%) refer to the set of questions at the end of Chapter 7, Chapter 8, Chapter 9 and Chapter 10. You are required to use your responses to build your computational essay. Each chapter provides more specific guidance of the tasks and discussion that you are required to consider in your assignment.\n\n1.5.1 Format Requirements\nBoth assignments will have the same requirements:\n\nMaximum word count: 2,000 words, excluding figures and references.\nUp to three maps, plot or figures (a figure may include more than one map and/or plot and will only count as one but needs to be integrated in the figure)\nUp to two tables.\n\nAssignments need to be prepared in “Quarto Document” format (i.e. qmd extension) and then converted into a self-contained HTML file that will then be submitted via Turnitin. The document should only display content that will be assessed. Intermediate steps do not need to be displayed. Messages resulting from loading packages, attaching data frames, or similar messages do not need to be included as output code. Useful resources to customise your R notebook can be found on Quarto’s website.\nTwo Quarto Document templates will be available via the module Canvas site.\nSubmission is electronic only via Turnitin on Canvas.\n\n1.5.2 Marking criteria\nThe Standard Environmental Sciences School marking criteria apply, with a stronger emphasis on evidencing the use of regression models, critical analysis of results and presentation standards. In addition to these general criteria, the code and outputs (i.e. tables, maps and plots) contained within the notebook submitted for assessment will be assessed according to the extent of documentation and evidence of expertise in changing and extending the code options illustrated in each chapter. Specifically, the following criteria will be applied:\n\n\n0-15: no documentation and use of default options.\n\n16-39: little documentation and use of default options.\n\n40-49: some documentation, and use of default options.\n\n50-59: extensive documentation, and edit of some of the options provided in the notebook (e.g. change north arrow location).\n\n60-69: extensive well organised and easy to read documentation, and evidence of understanding of options provided in the code (e.g. tweaking existing options).\n\n70-79: all above, plus clear evidence of code design skills (e.g. customising graphics, combining plots (or tables) into a single output, adding clear axis labels and variable names on graphic outputs, etc.).\n\n80-100: all as above, plus code containing novel contributions that extend/improve the functionality the code was provided with (e.g. comparative model assessments, novel methods to perform the task, etc.).", + "crumbs": [ + "1  Overview" + ] + }, + { + "objectID": "02-spatial_data.html", + "href": "02-spatial_data.html", + "title": "2  Spatial Data", + "section": "", + "text": "2.1 Spatial Data types\nDifferent classifications of spatial data types exist. Knowing the structure of the data at hand is important as specific analytical methods would be more appropriate for particular data types. We will use a particular classification involving four data types: lattice/areal data, point data, flow data and trajectory data (Fig. 1). This is not a exhaustive list but it is helpful to motivate the analytical and modelling methods that we cover in this book.\nLattice/Areal Data. These data correspond to records of attribute values (such as population counts) for a fixed geographical area. They may comprise regular shapes (such as grids or pixels) or irregular shapes (such as states, counties or travel-to-work areas). Raster data are a common source of regular lattice/areal area, while censuses are probably the most common form of irregular lattice/areal area. Point data within an area can be aggregated to produce lattice/areal data.\nPoint Data. These data refer to records of the geographic location of an discrete event, or the number of occurrences of geographical process at a given location. As displayed in Fig. 1, examples include the geographic location of bus stops in a city, or the number of boarding passengers at each bus stop.\nFlow Data. These data refer to records of measurements for a pair of geographic point locations. or pair of areas. These data capture the linkage or spatial interaction between two locations. Migration flows between a place of origin and a place of destination is an example of this type of data.\nTrajectory Data. These data record geographic locations of moving objects at various points in time. A trajectory is composed of a single string of data recording the geographic location of an object at various points in time and each record in the string contains a time stamp. These data are complex and can be classified into explicit trajectory data and implicit trajectory data. The former refer to well-structured data and record positions of objects continuously and intensively at uniform time intervals, such as GPS data. The latter is less structured and record data in relatively time point intervals, including sensor-based, network-based and signal-based data (Kong et al. 2018).\nIn this course, we cover analytical and modelling approaches for point, lattice/areal and flow data. While we do not explicitly analyse trajectory data, various of the analytical approaches described in this book can be extended to incorporate time, and can be applied to model these types of data. In Chapter 10, we describe approaches to analyse and model spatio-temporal data. These same methods can be applied to trajectory data.", + "crumbs": [ + "2  Spatial Data" + ] }, { "objectID": "02-spatial_data.html#spatial-data-types", "href": "02-spatial_data.html#spatial-data-types", "title": "2  Spatial Data", - "section": "2.1 Spatial Data types", - "text": "2.1 Spatial Data types\nDifferent classifications of spatial data types exist. Knowing the structure of the data at hand is important as specific analytical methods would be more appropriate for particular data types. We will use a particular classification involving four data types: lattice/areal data, point data, flow data and trajectory data (Fig. 1). This is not a exhaustive list but it is helpful to motivate the analytical and modelling methods that we cover in this book.\n\n\n\nFig. 1. Data Types. Area / Lattice data source: Önnerfors et al. (2019). Point data source: Tao et al. (2018). Flow data source: Rowe and Patias (2020). Trajectory data source: Kwan and Lee (2004).\n\n\nLattice/Areal Data. These data correspond to records of attribute values (such as population counts) for a fixed geographical area. They may comprise regular shapes (such as grids or pixels) or irregular shapes (such as states, counties or travel-to-work areas). Raster data are a common source of regular lattice/areal area, while censuses are probably the most common form of irregular lattice/areal area. Point data within an area can be aggregated to produce lattice/areal data.\nPoint Data. These data refer to records of the geographic location of an discrete event, or the number of occurrences of geographical process at a given location. As displayed in Fig. 1, examples include the geographic location of bus stops in a city, or the number of boarding passengers at each bus stop.\nFlow Data. These data refer to records of measurements for a pair of geographic point locations. or pair of areas. These data capture the linkage or spatial interaction between two locations. Migration flows between a place of origin and a place of destination is an example of this type of data.\nTrajectory Data. These data record geographic locations of moving objects at various points in time. A trajectory is composed of a single string of data recording the geographic location of an object at various points in time and each record in the string contains a time stamp. These data are complex and can be classified into explicit trajectory data and implicit trajectory data. The former refer to well-structured data and record positions of objects continuously and intensively at uniform time intervals, such as GPS data. The latter is less structured and record data in relatively time point intervals, including sensor-based, network-based and signal-based data (Kong et al. 2018).\nIn this course, we cover analytical and modelling approaches for point, lattice/areal and flow data. While we do not explicitly analyse trajectory data, various of the analytical approaches described in this book can be extended to incorporate time, and can be applied to model these types of data. In Chapter 10, we describe approaches to analyse and model spatio-temporal data. These same methods can be applied to trajectory data." + "section": "", + "text": "Fig. 1. Data Types. Area / Lattice data source: Önnerfors et al. (2019). Point data source: Tao et al. (2018). Flow data source: Rowe and Patias (2020). Trajectory data source: Kwan and Lee (2004).", + "crumbs": [ + "2  Spatial Data" + ] }, { "objectID": "02-spatial_data.html#hierarchical-structure-of-data", "href": "02-spatial_data.html#hierarchical-structure-of-data", "title": "2  Spatial Data", - "section": "2.2 Hierarchical Structure of Data", - "text": "2.2 Hierarchical Structure of Data\nThe hierarchical organisation is a key feature of spatial data. Smaller geographical units are organised within larger geographical units. You can find the hierarchical representation of UK Statistical Geographies on the Office for National Statistics website. In the bottom part of the output below, we can observe a spatial data frame for Liverpool displaying the hierarchical structure of census data (from the smallest to the largest): Output Areas (OAs), Lower Super Output Areas (LSOAs), Middle Super Output Areas (MSOAs) and Local Authority Districts (LADs). This hierarchical structure entails that units in smaller geographies are nested within units in larger geographies, and that smaller units can be aggregated to produce large units.\n\n\n\n\n\nSimple feature collection with 6 features and 4 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335071.6 ymin: 389876.7 xmax: 339426.9 ymax: 394479\nProjected CRS: Transverse_Mercator\n OA_CD LSOA_CD MSOA_CD LAD_CD geometry\n1 E00176737 E01033761 E02006932 E08000012 MULTIPOLYGON (((335106.3 38...\n2 E00033515 E01006614 E02001358 E08000012 MULTIPOLYGON (((335810.5 39...\n3 E00033141 E01006546 E02001365 E08000012 MULTIPOLYGON (((336738 3931...\n4 E00176757 E01006646 E02001369 E08000012 MULTIPOLYGON (((335914.5 39...\n5 E00034050 E01006712 E02001375 E08000012 MULTIPOLYGON (((339325 3914...\n6 E00034280 E01006761 E02001366 E08000012 MULTIPOLYGON (((338198.1 39...\n\n\nNext we quickly go through the components of the output above. The first line indicates the type of feature and the number of rows (features) and columns (fields) in the data frame, except for the geometry. The second and third lines identify the type of geometry and dimension. The fourth line bbox stands for bounding box and display the min and max coordinates containing the Liverpool area in the data frame. The fifth line projected CRS indicates the coordinate reference system projection. If you would like to learn more about the various components of spatial data frames, please see the R sf package vignette on Simple Features." + "section": "\n2.2 Hierarchical Structure of Data", + "text": "2.2 Hierarchical Structure of Data\nThe hierarchical organisation is a key feature of spatial data. Smaller geographical units are organised within larger geographical units. You can find the hierarchical representation of UK Statistical Geographies on the Office for National Statistics website. In the bottom part of the output below, we can observe a spatial data frame for Liverpool displaying the hierarchical structure of census data (from the smallest to the largest): Output Areas (OAs), Lower Super Output Areas (LSOAs), Middle Super Output Areas (MSOAs) and Local Authority Districts (LADs). This hierarchical structure entails that units in smaller geographies are nested within units in larger geographies, and that smaller units can be aggregated to produce large units.\n\n\nSimple feature collection with 6 features and 4 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335071.6 ymin: 389876.7 xmax: 339426.9 ymax: 394479\nProjected CRS: Transverse_Mercator\n OA_CD LSOA_CD MSOA_CD LAD_CD geometry\n1 E00176737 E01033761 E02006932 E08000012 MULTIPOLYGON (((335106.3 38...\n2 E00033515 E01006614 E02001358 E08000012 MULTIPOLYGON (((335810.5 39...\n3 E00033141 E01006546 E02001365 E08000012 MULTIPOLYGON (((336738 3931...\n4 E00176757 E01006646 E02001369 E08000012 MULTIPOLYGON (((335914.5 39...\n5 E00034050 E01006712 E02001375 E08000012 MULTIPOLYGON (((339325 3914...\n6 E00034280 E01006761 E02001366 E08000012 MULTIPOLYGON (((338198.1 39...\n\n\nNext we quickly go through the components of the output above. The first line indicates the type of feature and the number of rows (features) and columns (fields) in the data frame, except for the geometry. The second and third lines identify the type of geometry and dimension. The fourth line bbox stands for bounding box and display the min and max coordinates containing the Liverpool area in the data frame. The fifth line projected CRS indicates the coordinate reference system projection. If you would like to learn more about the various components of spatial data frames, please see the R sf package vignette on Simple Features.", + "crumbs": [ + "2  Spatial Data" + ] }, { "objectID": "02-spatial_data.html#key-challenges", "href": "02-spatial_data.html#key-challenges", "title": "2  Spatial Data", - "section": "2.3 Key Challenges", - "text": "2.3 Key Challenges\nMajor challenges exist when working with spatial data. Below we explore some of the key longstanding problems data scientists often face when working with geographical data.\n\n2.3.1 Modifible Area Unit Problem (MAUP)\nThe Modifible Area Unit Problem (MAUP) represents a challenge that has troubled geographers for decades (Openshaw 1981). Two aspects of the MAUP are normally recognised in empirical analysis relating to scale and zonation. Fig. 2 illustrates these issues\n\nScale refers to the idea that a geographical area can be divided into geographies with differing numbers of spatial units.\nZonation refers to the idea that a geographical area can be divided into the same number of units in a variety of ways.\n\n\n\n\nFig. 2. MAUP effect. (a) scale effect; and, (b) zonation effect. Source: Loidl et al. (2016).\n\n\nThe MAUP is a critical issue as it can impact our analysis and thus any conclusions we can infer from our results (e.g. Fotheringham and Wong 1991). There is no agreed systematic approach on how to handle the effects of the MAUP. Some have suggested to perform analyses based on different existing geographical scales, and assess the consistency of the results and identify potential sources of change. The issue with such approach is that results from analysis at different scales are likely to differ because distinct dimensions of a geographic process may be captured at different scales. For example, in migration studies, smaller geographies may be more suitable to capture residential mobility over short distances, while large geographies may be more suitable to capture long-distance migration. And it is well documented that these types of moves are driven by different factors. While residential mobility tends to be driven by housing related reasons, long-distance migration is more closely related to employment-related motives (Niedomysl 2011).\nAn alternative approach is to use the smallest geographical system available and create random aggregations at various geographical scales, to directly quantify the extent of scale and zonation. This approach has shown promising results in applications to study internal migration flows (Stillwell, Daras, and Bell 2018). Another approach involves the production of “meaningful” or functional geographies that can more appropriately capture the process of interest. There is an active area of work defining functional labour markets (Casado-Díaz, Martínez-Bernabéu, and Rowe 2017), urban areas (Arribas-Bel, Garcia-López, and Viladecans-Marsal 2021) and various forms of geodemographic classifications (Singleton and Spielman 2013; Patias, Rowe, and Cavazzi 2019) . However there is the recognition that none of the existing approaches resolve the effects of the MAUP and recently it has been suggested that the most plausible ‘solution’ would be to ignore the MAUP (Wolf et al. 2020).\n\n\n2.3.2 Ecological Fallacy\nEcological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is Robinson (1950) who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. Robinson (1950) used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was 0.53. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative -0.11 which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data.\n\n\n2.3.3 Spatial Dependence\nSpatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that these values are more similar (or less similar) than expected for randomly associated pairs of observations (Anselin 1988). For example, we could think of observed patterns of ethnic segregation in an area are a result of spillover effects of pre-existing patterns of ethnic segregation in neighbouring areas. Chapter 5 will illustrate approach to explicitly incorporate spatial dependence in regression analysis.\n\n\n2.3.4 Spatial Heterogeneity\nSpatial heterogeneity refers to the uneven distribution of a variable’s values across space. Concentration of deprivation or unemployment across an area are good examples of spatial heterogeneity. We illustrate various ways to visualise, explore and measure the spatial distribution of data in multiple chapters. We also discuss on potential modelling approaches to capture spatial heterogeneity in Chapter 5, Chapter 7 and Chapter 10.\n\n\n2.3.5 Spatial nonstationarity\nSpatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space. In a modelling context, it relates to a situation in which a simple “global” model is inappropriate to explain the relationships between a set of variables. The geographical nature of the model must be modified to reflect local structural relationships within the data. For example, ethinic segregation has been positively associated with employment outcomes in some countries pointing to networks in pre-existing communities facilitating access to the local labour market. Inversely ethinic segregation has been negatively associated with employment outcomes pointing to lack of integration into the broader local community. We illustrate various modelling approaches to capture spatial nonstationarity in Chapter 8 and Chapter 9.\n\n\n\n\nAnselin, Luc. 1988. Spatial Econometrics: Methods and Models. Vol. 4. Springer Science & Business Media.\n\n\nArribas-Bel, Daniel, M.-À. Garcia-López, and Elisabet Viladecans-Marsal. 2021. “Building(s and) Cities: Delineating Urban Areas with a Machine Learning Algorithm.” Journal of Urban Economics 125 (September): 103217. https://doi.org/10.1016/j.jue.2019.103217.\n\n\nCasado-Díaz, José Manuel, Lucas Martínez-Bernabéu, and Francisco Rowe. 2017. “An Evolutionary Approach to the Delimitation of Labour Market Areas: An Empirical Application for Chile.” Spatial Economic Analysis 12 (4): 379–403. https://doi.org/10.1080/17421772.2017.1273541.\n\n\nFotheringham, A Stewart, and David WS Wong. 1991. “The Modifiable Areal Unit Problem in Multivariate Statistical Analysis.” Environment and Planning A 23 (7): 1025–44.\n\n\nKong, Xiangjie, Menglin Li, Kai Ma, Kaiqi Tian, Mengyuan Wang, Zhaolong Ning, and Feng Xia. 2018. “Big Trajectory Data: A Survey of Applications and Services.” IEEE Access 6: 58295–306. https://doi.org/10.1109/access.2018.2873779.\n\n\nKwan, Mei-Po, and Jiyeong Lee. 2004. “Geovisualization of Human Activity Patterns Using 3D GIS: A Time-Geographic Approach.” Spatially Integrated Social Science 27: 721–44.\n\n\nLoidl, Martin, Gudrun Wallentin, Robin Wendel, and Bernhard Zagel. 2016. “Mapping Bicycle Crash Risk Patterns on the Local Scale.” Safety 2 (3): 17. https://doi.org/10.3390/safety2030017.\n\n\nNiedomysl, Thomas. 2011. “How Migration Motives Change over Migration Distance: Evidence on Variation Across Socio-Economic and Demographic Groups.” Regional Studies 45 (6): 843–55. https://doi.org/10.1080/00343401003614266.\n\n\nÖnnerfors, Åsa, Mariana Kotzeva, Teodóra Brandmüller, et al. 2019. “Eurostat Regional Yearbook 2019 Edition.”\n\n\nOpenshaw, Stan. 1981. “The Modifiable Areal Unit Problem.” Quantitative Geography: A British View, 60–69.\n\n\nPatias, Nikos, Francisco Rowe, and Stefano Cavazzi. 2019. “A Scalable Analytical Framework for Spatio-Temporal Analysis of Neighborhood Change: A Sequence Analysis Approach.” In, 223–41. Springer International Publishing. https://doi.org/10.1007/978-3-030-14745-7_13.\n\n\nRobinson, WS. 1950. “Ecological Correlations and Individual Behavior.” American Sociological Review 15 (195): 351–57.\n\n\nRowe, Francisco, and Nikos Patias. 2020. “Mapping the Spatial Patterns of Internal Migration in Europe.” Regional Studies, Regional Science 7 (1): 390–93. https://doi.org/10.1080/21681376.2020.1811139.\n\n\nSingleton, Alexander D., and Seth E. Spielman. 2013. “The Past, Present, and Future of Geodemographic Research in the United States and United Kingdom.” The Professional Geographer 66 (4): 558–67. https://doi.org/10.1080/00330124.2013.848764.\n\n\nStillwell, John, Konstantinos Daras, and Martin Bell. 2018. “Spatial Aggregation Methods for Investigating the MAUP Effects in Migration Analysis.” Applied Spatial Analysis and Policy 11 (4): 693–711. https://doi.org/10.1007/s12061-018-9274-6.\n\n\nTao, Sui, Jonathan Corcoran, Francisco Rowe, and Mark Hickman. 2018. “To Travel or Not to Travel: ‘Weather’ Is the Question. Modelling the Effect of Local Weather Conditions on Bus Ridership.” Transportation Research Part C: Emerging Technologies 86 (January): 147–67. https://doi.org/10.1016/j.trc.2017.11.005.\n\n\nWolf, Levi John, Sean Fox, Rich Harris, Ron Johnston, Kelvyn Jones, David Manley, Emmanouil Tranos, and Wenfei Winnie Wang. 2020. “Quantitative Geography III: Future Challenges and Challenging Futures.” Progress in Human Geography 45 (3): 596–608. https://doi.org/10.1177/0309132520924722." + "section": "\n2.3 Key Challenges", + "text": "2.3 Key Challenges\nMajor challenges exist when working with spatial data. Below we explore some of the key longstanding problems data scientists often face when working with geographical data.\n\n2.3.1 Modifible Area Unit Problem (MAUP)\nThe Modifible Area Unit Problem (MAUP) represents a challenge that has troubled geographers for decades (Openshaw 1981). Two aspects of the MAUP are normally recognised in empirical analysis relating to scale and zonation. Fig. 2 illustrates these issues\n\nScale refers to the idea that a geographical area can be divided into geographies with differing numbers of spatial units.\nZonation refers to the idea that a geographical area can be divided into the same number of units in a variety of ways.\n\n\n\nFig. 2. MAUP effect. (a) scale effect; and, (b) zonation effect. Source: Loidl et al. (2016).\n\nThe MAUP is a critical issue as it can impact our analysis and thus any conclusions we can infer from our results (e.g. Fotheringham and Wong 1991). There is no agreed systematic approach on how to handle the effects of the MAUP. Some have suggested to perform analyses based on different existing geographical scales, and assess the consistency of the results and identify potential sources of change. The issue with such approach is that results from analysis at different scales are likely to differ because distinct dimensions of a geographic process may be captured at different scales. For example, in migration studies, smaller geographies may be more suitable to capture residential mobility over short distances, while large geographies may be more suitable to capture long-distance migration. And it is well documented that these types of moves are driven by different factors. While residential mobility tends to be driven by housing related reasons, long-distance migration is more closely related to employment-related motives (Niedomysl 2011).\nAn alternative approach is to use the smallest geographical system available and create random aggregations at various geographical scales, to directly quantify the extent of scale and zonation. This approach has shown promising results in applications to study internal migration flows (Stillwell, Daras, and Bell 2018). Another approach involves the production of “meaningful” or functional geographies that can more appropriately capture the process of interest. There is an active area of work defining functional labour markets (Casado-Díaz, Martínez-Bernabéu, and Rowe 2017), urban areas (Arribas-Bel, Garcia-López, and Viladecans-Marsal 2021) and various forms of geodemographic classifications (Singleton and Spielman 2013; Patias, Rowe, and Cavazzi 2019) . However there is the recognition that none of the existing approaches resolve the effects of the MAUP and recently it has been suggested that the most plausible ‘solution’ would be to ignore the MAUP (Wolf et al. 2020).\n\n2.3.2 Ecological Fallacy\nEcological fallacy is an error in the interpretation of statistical data based on aggregate information. Specifically it refers to inferences made about the nature of specific individuals based solely on statistics aggregated for a given group. It is about thinking that relationships observed for groups necessarily hold for individuals. A key example is Robinson (1950) who illustrates this problem exploring the difference between ecological correlations and individual correlations. He looked at the relationship between country of birth and literacy. Robinson (1950) used the percent of foreign-born population and percent of literate population for the 48 states in the United States in 1930. The ecological correlation based on these data was 0.53. This suggests a positive association between foreign birth and literacy, and could be interpreted as foreign born individuals being more likely to be literate than native-born individuals. Yet, the correlation based on individual data was negative -0.11 which indicates the opposite. The main point emerging from this example is to carefully interpret analysis based on spatial data and avoid making inferences about individuals from these data.\n\n2.3.3 Spatial Dependence\nSpatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that these values are more similar (or less similar) than expected for randomly associated pairs of observations (Anselin 1988). For example, we could think of observed patterns of ethnic segregation in an area are a result of spillover effects of pre-existing patterns of ethnic segregation in neighbouring areas. Chapter 5 will illustrate approach to explicitly incorporate spatial dependence in regression analysis.\n\n2.3.4 Spatial Heterogeneity\nSpatial heterogeneity refers to the uneven distribution of a variable’s values across space. Concentration of deprivation or unemployment across an area are good examples of spatial heterogeneity. We illustrate various ways to visualise, explore and measure the spatial distribution of data in multiple chapters. We also discuss on potential modelling approaches to capture spatial heterogeneity in Chapter 5, Chapter 7 and Chapter 10.\n\n2.3.5 Spatial nonstationarity\nSpatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space. In a modelling context, it relates to a situation in which a simple “global” model is inappropriate to explain the relationships between a set of variables. The geographical nature of the model must be modified to reflect local structural relationships within the data. For example, ethinic segregation has been positively associated with employment outcomes in some countries pointing to networks in pre-existing communities facilitating access to the local labour market. Inversely ethinic segregation has been negatively associated with employment outcomes pointing to lack of integration into the broader local community. We illustrate various modelling approaches to capture spatial nonstationarity in Chapter 8 and Chapter 9.\n\n\n\n\n\n\nAnselin, Luc. 1988. Spatial Econometrics: Methods and Models. Vol. 4. Springer Science & Business Media.\n\n\nArribas-Bel, Daniel, M.-À. Garcia-López, and Elisabet Viladecans-Marsal. 2021. “Building(s and) Cities: Delineating Urban Areas with a Machine Learning Algorithm.” Journal of Urban Economics 125 (September): 103217. https://doi.org/10.1016/j.jue.2019.103217.\n\n\nCasado-Díaz, José Manuel, Lucas Martínez-Bernabéu, and Francisco Rowe. 2017. “An Evolutionary Approach to the Delimitation of Labour Market Areas: An Empirical Application for Chile.” Spatial Economic Analysis 12 (4): 379–403. https://doi.org/10.1080/17421772.2017.1273541.\n\n\nFotheringham, A Stewart, and David WS Wong. 1991. “The Modifiable Areal Unit Problem in Multivariate Statistical Analysis.” Environment and Planning A 23 (7): 1025–44.\n\n\nKong, Xiangjie, Menglin Li, Kai Ma, Kaiqi Tian, Mengyuan Wang, Zhaolong Ning, and Feng Xia. 2018. “Big Trajectory Data: A Survey of Applications and Services.” IEEE Access 6: 58295–306. https://doi.org/10.1109/access.2018.2873779.\n\n\nKwan, Mei-Po, and Jiyeong Lee. 2004. “Geovisualization of Human Activity Patterns Using 3D GIS: A Time-Geographic Approach.” Spatially Integrated Social Science 27: 721–44.\n\n\nLoidl, Martin, Gudrun Wallentin, Robin Wendel, and Bernhard Zagel. 2016. “Mapping Bicycle Crash Risk Patterns on the Local Scale.” Safety 2 (3): 17. https://doi.org/10.3390/safety2030017.\n\n\nNiedomysl, Thomas. 2011. “How Migration Motives Change over Migration Distance: Evidence on Variation Across Socio-Economic and Demographic Groups.” Regional Studies 45 (6): 843–55. https://doi.org/10.1080/00343401003614266.\n\n\nÖnnerfors, Åsa, Mariana Kotzeva, Teodóra Brandmüller, et al. 2019. “Eurostat Regional Yearbook 2019 Edition.”\n\n\nOpenshaw, Stan. 1981. “The Modifiable Areal Unit Problem.” Quantitative Geography: A British View, 60–69.\n\n\nPatias, Nikos, Francisco Rowe, and Stefano Cavazzi. 2019. “A Scalable Analytical Framework for Spatio-Temporal Analysis of Neighborhood Change: A Sequence Analysis Approach.” In, 223–41. Springer International Publishing. https://doi.org/10.1007/978-3-030-14745-7_13.\n\n\nRobinson, WS. 1950. “Ecological Correlations and Individual Behavior.” American Sociological Review 15 (195): 351–57.\n\n\nRowe, Francisco, and Nikos Patias. 2020. “Mapping the Spatial Patterns of Internal Migration in Europe.” Regional Studies, Regional Science 7 (1): 390–93. https://doi.org/10.1080/21681376.2020.1811139.\n\n\nSingleton, Alexander D., and Seth E. Spielman. 2013. “The Past, Present, and Future of Geodemographic Research in the United States and United Kingdom.” The Professional Geographer 66 (4): 558–67. https://doi.org/10.1080/00330124.2013.848764.\n\n\nStillwell, John, Konstantinos Daras, and Martin Bell. 2018. “Spatial Aggregation Methods for Investigating the MAUP Effects in Migration Analysis.” Applied Spatial Analysis and Policy 11 (4): 693–711. https://doi.org/10.1007/s12061-018-9274-6.\n\n\nTao, Sui, Jonathan Corcoran, Francisco Rowe, and Mark Hickman. 2018. “To Travel or Not to Travel: ‘Weather’ Is the Question. Modelling the Effect of Local Weather Conditions on Bus Ridership.” Transportation Research Part C: Emerging Technologies 86 (January): 147–67. https://doi.org/10.1016/j.trc.2017.11.005.\n\n\nWolf, Levi John, Sean Fox, Rich Harris, Ron Johnston, Kelvyn Jones, David Manley, Emmanouil Tranos, and Wenfei Winnie Wang. 2020. “Quantitative Geography III: Future Challenges and Challenging Futures.” Progress in Human Geography 45 (3): 596–608. https://doi.org/10.1177/0309132520924722.", + "crumbs": [ + "2  Spatial Data" + ] + }, + { + "objectID": "03-data-wrangling.html", + "href": "03-data-wrangling.html", + "title": "3  Data Wrangling", + "section": "", + "text": "3.1 Dependencies\nThis chapter uses the libraries below. Ensure they are installed on your machine1 before you progress.\n# data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# nice tables\nlibrary(kableExtra)\n# spatial data manipulation\nlibrary(sf) \n# thematic mapping\nlibrary(tmap) \n# colour palettes\nlibrary(RColorBrewer) \nlibrary(viridis)", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#dependencies", "href": "03-data-wrangling.html#dependencies", "title": "3  Data Wrangling", - "section": "3.1 Dependencies", - "text": "3.1 Dependencies\nThis tutorial uses the libraries below. Ensure they are installed on your machine2 before loading them executing the following code chunk:\n\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(RColorBrewer) \n# More colour palettes\nlibrary(viridis)" + "section": "", + "text": "1 You can install package mypackage by running the command install.packages(\"mypackage\") on the R prompt or through the Tools --> Install Packages... menu in RStudio.", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#introducing-r", "href": "03-data-wrangling.html#introducing-r", "title": "3  Data Wrangling", - "section": "3.2 Introducing R", - "text": "3.2 Introducing R\nR is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on CRAN. CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R.\nCommands are sent to R using either the terminal / command line or the R Console which is installed with R on either Windows or OS X. On Linux, there is no equivalent of the console, however, third party solutions exist. On your own machine, R can be installed from here.\nNormally RStudio is used to implement R coding. RStudio is an integrated development environment (IDE) for R and provides a more user-friendly front-end to R than the front-end provided with R.\nTo run R or RStudio, just double click on the R or RStudio icon. Throughout this module, we will be using RStudio:\n\n\n\nFig. 1. RStudio features.\n\n\nIf you would like to know more about the various features of RStudio, watch this video" + "section": "\n3.2 Introducing R", + "text": "3.2 Introducing R\nR is a freely available language and environment for statistical computing and graphics which provides a wide variety of statistical and graphical techniques. It has gained widespread use in academia and industry. R offers a wider array of functionality than a traditional statistics package, such as SPSS and is composed of core (base) functionality, and is expandable through libraries hosted on The Comprehensive R Archive Network (CRAN). CRAN is a network of ftp and web servers around the world that store identical, up-to-date, versions of code and documentation for R.\nCommands are sent to R using either the terminal / command line or the R Console which is installed with R on either Windows or OS X. On Linux, there is no equivalent of the console, however, third party solutions exist. On your own machine, R can be installed from here.\nNormally RStudio is used to implement R coding. RStudio is an integrated development environment (IDE) for R and provides a more user-friendly front-end to R than the front-end provided with R.\nTo run R or RStudio, just double click on the R or RStudio icon. Throughout this module, we will be using RStudio:\n\n\nFig. 1. RStudio features.\n\nIf you would like to know more about the various features of RStudio, watch this video", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#setting-the-working-directory", "href": "03-data-wrangling.html#setting-the-working-directory", "title": "3  Data Wrangling", - "section": "3.3 Setting the working directory", - "text": "3.3 Setting the working directory\nBefore we start any analysis, ensure to set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the data folder lives.\n\n#setwd('../data/sar.csv')\n#setwd('.')\n\nNote: It is good practice to not include spaces when naming folders and files. Use underscores or dots.\nYou can check your current working directory by typing:\n\ngetwd()\n\n[1] \"/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san\"" + "section": "\n3.3 Setting the working directory", + "text": "3.3 Setting the working directory\nBefore we start any analysis, ensure to set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the data folder lives.\n\nsetwd('../data/sar.csv')\nsetwd('.')\n\n\n\n\n\n\n\n\n\nNote\n\n\n\nIt is good practice to not include spaces when naming folders and files. Use underscores or dots.\n\n\nYou can check your current working directory by typing:\n\ngetwd()\n\n[1] \"/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san\"", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#r-scripts-and-computational-notebooks", "href": "03-data-wrangling.html#r-scripts-and-computational-notebooks", "title": "3  Data Wrangling", - "section": "3.4 R Scripts and Computational Notebooks", - "text": "3.4 R Scripts and Computational Notebooks\nAn R script is a series of commands that you can execute at one time and help you save time. So you do not repeat the same steps every time you want to execute the same process with different datasets. An R script is just a plain text file with R commands in it.\nTo create an R script in RStudio, you need to\n\nOpen a new script file: File > New File > R Script\nWrite some code on your new script window by typing eg. mtcars\nRun the script. Click anywhere on the line of code, then hit Ctrl + Enter (Windows) or Cmd + Enter (Mac) to run the command or select the code chunk and click run on the right-top corner of your script window. If do that, you should get:\n\n\nmtcars\n\n mpg cyl disp hp drat wt qsec vs am gear carb\nMazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4\nMazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4\nDatsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1\nHornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1\nHornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2\nValiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1\nDuster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4\nMerc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2\nMerc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2\nMerc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4\nMerc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4\nMerc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3\nMerc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3\nMerc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3\nCadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4\nLincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4\nChrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4\nFiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1\nHonda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2\nToyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1\nToyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1\nDodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2\nAMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2\nCamaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4\nPontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2\nFiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1\nPorsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2\nLotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2\nFord Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4\nFerrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6\nMaserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8\nVolvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2\n\n\n\nSave the script: File > Save As, select your required destination folder, and enter any filename that you like, provided that it ends with the file extension .R\n\nAn R Notebook is an R Markdown document with descriptive text and code chunks that can be executed independently and interactively, with output visible immediately beneath a code chunk - see Xie, Allaire, and Grolemund (2019).\nTo create an R Notebook, you need to:\n\nOpen a new script file: File > New File > R Notebook\n\n\n\n\nFig. 2. YAML metadata for notebooks.\n\n\n\nInsert code chunks, either:\n\n\nuse the Insert command on the editor toolbar;\nuse the keyboard shortcut Ctrl + Alt + I or Cmd + Option + I (Mac); or,\ntype the chunk delimiters ```{r} and ```\n\nIn a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets eg.\n\n\n\nFig. 3. Code chunk example. Details on the various options: https://rmarkdown.rstudio.com/lesson-3.html\n\n\n\nExecute code: hit “Run Current Chunk”, Ctrl + Shift + Enter or Cmd + Shift + Enter (Mac)\nSave an R notebook: File > Save As. A notebook has a *.Rmd extension and when it is saved a *.nb.html file is automatically created. The latter is a self-contained HTML file which contains both a rendered copy of the notebook with all current chunk outputs and a copy of the *.Rmd file itself.\n\nRstudio also offers a Preview option on the toolbar which can be used to create pdf, html and word versions of the notebook. To do this, choose from the drop-down list menu knit to ...\nFor this module, we will be using computational notebooks through Quarto; that is, Quarto Document. “Quarto is a multi-language, next generation version of R Markdown from RStudio, with many new new features and capabilities. Like R Markdown, Quarto uses Knitr to execute R code, and is therefore able to render most existing Rmd files without modification.”\nTo create a Quarto Document, you need to:\n\nOpen a new script file: File > New File > Quarto Document\n\nQuarto Documents work in the same way as R Notebooks with small variations. You find a comprehensive guide on the Quarto website." + "section": "\n3.4 R Scripts and Computational Notebooks", + "text": "3.4 R Scripts and Computational Notebooks\nAn R script is a series of commands that you can execute at one time and help you save time. So you do not repeat the same steps every time you want to execute the same process with different datasets. An R script is just a plain text file with R commands in it.\n\n\n\n\n\n\n\n\nNote\n\n\n\nTo get familiar with good practices in writing your code in R, we recommend the Chapter Workflow: basics and Workflow: scripts and projects from the R in Data Science book by Wickham, Çetinkaya-Rundel, and Grolemund (2023).\n\n\nhttps://r4ds.hadley.nz/workflow-basics.html\nTo create an R script in RStudio, you need to\n\nOpen a new script file: File > New File > R Script\nWrite some code on your new script window by typing eg. mtcars\nRun the script. Click anywhere on the line of code, then hit Ctrl + Enter (Windows) or Cmd + Enter (Mac) to run the command or select the code chunk and click run on the right-top corner of your script window. If do that, you should get:\n\n\nmtcars\n\n mpg cyl disp hp drat wt qsec vs am gear carb\nMazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0 1 4 4\nMazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0 1 4 4\nDatsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1\nHornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1 0 3 1\nHornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0 0 3 2\nValiant 18.1 6 225.0 105 2.76 3.460 20.22 1 0 3 1\nDuster 360 14.3 8 360.0 245 3.21 3.570 15.84 0 0 3 4\nMerc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2\nMerc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2\nMerc 280 19.2 6 167.6 123 3.92 3.440 18.30 1 0 4 4\nMerc 280C 17.8 6 167.6 123 3.92 3.440 18.90 1 0 4 4\nMerc 450SE 16.4 8 275.8 180 3.07 4.070 17.40 0 0 3 3\nMerc 450SL 17.3 8 275.8 180 3.07 3.730 17.60 0 0 3 3\nMerc 450SLC 15.2 8 275.8 180 3.07 3.780 18.00 0 0 3 3\nCadillac Fleetwood 10.4 8 472.0 205 2.93 5.250 17.98 0 0 3 4\nLincoln Continental 10.4 8 460.0 215 3.00 5.424 17.82 0 0 3 4\nChrysler Imperial 14.7 8 440.0 230 3.23 5.345 17.42 0 0 3 4\nFiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1\nHonda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2\nToyota Corolla 33.9 4 71.1 65 4.22 1.835 19.90 1 1 4 1\nToyota Corona 21.5 4 120.1 97 3.70 2.465 20.01 1 0 3 1\nDodge Challenger 15.5 8 318.0 150 2.76 3.520 16.87 0 0 3 2\nAMC Javelin 15.2 8 304.0 150 3.15 3.435 17.30 0 0 3 2\nCamaro Z28 13.3 8 350.0 245 3.73 3.840 15.41 0 0 3 4\nPontiac Firebird 19.2 8 400.0 175 3.08 3.845 17.05 0 0 3 2\nFiat X1-9 27.3 4 79.0 66 4.08 1.935 18.90 1 1 4 1\nPorsche 914-2 26.0 4 120.3 91 4.43 2.140 16.70 0 1 5 2\nLotus Europa 30.4 4 95.1 113 3.77 1.513 16.90 1 1 5 2\nFord Pantera L 15.8 8 351.0 264 4.22 3.170 14.50 0 1 5 4\nFerrari Dino 19.7 6 145.0 175 3.62 2.770 15.50 0 1 5 6\nMaserati Bora 15.0 8 301.0 335 3.54 3.570 14.60 0 1 5 8\nVolvo 142E 21.4 4 121.0 109 4.11 2.780 18.60 1 1 4 2\n\n\n\nSave the script: File > Save As, select your required destination folder, and enter any filename that you like, provided that it ends with the file extension .R\n\n\nAn R Notebook or a Quarto Document are a Markdown options with descriptive text and code chunks that can be executed independently and interactively, with output visible immediately beneath a code chunk - see Xie, Allaire, and Grolemund (2019). A Quarto Document is an improved version of the original R Notebook. Quarto Document requires a package called Quarto. Quarto does not have a dependency or requirement for R. Quarto is multilingual, beginning with R, Python, Javascript, and Julia. The concept is that Quarto will work even for languages that do not yet exist. This book was original written in R Notebook but later transitioned into Quarto Documents.\nTo create an R Notebook, you need to:\n\nOpen a new script file: File > New File > R Notebook\n\n\n\n\nFig. 2. YAML metadata for notebooks.\n\n\nInsert code chunks, either:\n\n\nuse the Insert command on the editor toolbar;\nuse the keyboard shortcut Ctrl + Alt + I or Cmd + Option + I (Mac); or,\ntype the chunk delimiters ```{r} and ```\n\n\nIn a chunk code you can produce text output, tables, graphics and write code! You can control these outputs via chunk options which are provided inside the curly brackets e.g.:\n\n\nFig. 3. Code chunk example. Details on the various options: https://rmarkdown.rstudio.com/lesson-3.html\n\n\nExecute code: hit “Run Current Chunk”, Ctrl + Shift + Enter or Cmd + Shift + Enter (Mac)\nSave an R notebook: File > Save As. A notebook has a *.Rmd extension and when it is saved a *.nb.html file is automatically created. The latter is a self-contained HTML file which contains both a rendered copy of the notebook with all current chunk outputs and a copy of the *.Rmd file itself.\n\nRstudio also offers a Preview option on the toolbar which can be used to create pdf, html and word versions of the notebook. To do this, choose from the drop-down list menu knit to ...\nTo create a Quarto Document, you need to:\n\nOpen a new script file: File > New File > Quarto Document\n\n\nQuarto Documents work in the same way as R Notebooks with small variations. You find a comprehensive guide on the Quarto website.", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#getting-help", "href": "03-data-wrangling.html#getting-help", "title": "3  Data Wrangling", - "section": "3.5 Getting Help", - "text": "3.5 Getting Help\nYou can use help or ? to ask for details for a specific function:\n\nhelp(sqrt) #or ?sqrt\n\nAnd using example provides examples for said function:\n\nexample(sqrt)\n\n\nsqrt> require(stats) # for spline\n\nsqrt> require(graphics)\n\nsqrt> xx <- -9:9\n\nsqrt> plot(xx, sqrt(abs(xx)), col = \"red\")\n\n\n\n\n\nExample sqrt\n\n\n\n\n\nsqrt> lines(spline(xx, sqrt(abs(xx)), n=101), col = \"pink\")" + "section": "\n3.5 Getting Help", + "text": "3.5 Getting Help\nYou can use help or ? to ask for details for a specific function:\n\nhelp(sqrt) #or\n?sqrt\n\nAnd using example provides examples for said function:\n\nexample(sqrt)\n\n\nsqrt> require(stats) # for spline\n\nsqrt> require(graphics)\n\nsqrt> xx <- -9:9\n\nsqrt> plot(xx, sqrt(abs(xx)), col = \"red\")\n\n\n\n\n\n\n\n\n\nsqrt> lines(spline(xx, sqrt(abs(xx)), n=101), col = \"pink\")", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#variables-and-objects", "href": "03-data-wrangling.html#variables-and-objects", "title": "3  Data Wrangling", - "section": "3.6 Variables and objects", - "text": "3.6 Variables and objects\nAn object is a data structure having attributes and methods. In fact, everything in R is an object!\nA variable is a type of data object. Data objects also include list, vector, matrices and text.\n\nCreating a data object\n\nIn R a variable can be created by using the symbol <- to assign a value to a variable name. The variable name is entered on the left <- and the value on the right. Note: Data objects can be given any name, provided that they start with a letter of the alphabet, and include only letters of the alphabet, numbers and the characters . and _. Hence AgeGroup, Age_Group and Age.Group are all valid names for an R data object. Note also that R is case-sensitive, to agegroup and AgeGroup would be treated as different data objects.\nTo save the value 28 to a variable (data object) labelled age, run the code:\n\nage <- 28\n\n\nInspecting a data object\n\nTo inspect the contents of the data object age run the following line of code:\n\nage\n\n[1] 28\n\n\nFind out what kind (class) of data object age is using:\n\nclass(age) \n\n[1] \"numeric\"\n\n\nInspect the structure of the age data object:\n\nstr(age) \n\n num 28\n\n\n\nThe vector data object\n\nWhat if we have more than one response? We can use the c( ) function to combine multiple values into one data vector object:\n\nage <- c(28, 36, 25, 24, 32)\nage\n\n[1] 28 36 25 24 32\n\nclass(age) #Still numeric..\n\n[1] \"numeric\"\n\nstr(age) #..but now a vector (set) of 5 separate values\n\n num [1:5] 28 36 25 24 32\n\n\nNote that on each line in the code above any text following the # character is ignored by R when executing the code. Instead, text following a # can be used to add comments to the code to make clear what the code is doing. Two marks of good code are a clear layout and clear commentary on the code.\n\n3.6.1 Basic Data Types\nThere are a number of data types. Four are the most common. In R, numeric is the default type for numbers. It stores all numbers as floating-point numbers (numbers with decimals). This is because most statistical calculations deal with numbers with up to two decimals.\n\nNumeric\n\n\nnum <- 4.5 # Decimal values\nclass(num)\n\n[1] \"numeric\"\n\n\n\nInteger\n\n\nint <- as.integer(4) # Natural numbers. Note integers are also numerics.\nclass(int)\n\n[1] \"integer\"\n\n\n\nCharacter\n\n\ncha <- \"are you enjoying this?\" # text or string. You can also type `as.character(\"are you enjoying this?\")`\nclass(cha)\n\n[1] \"character\"\n\n\n\nLogical\n\n\nlog <- 2 < 1 # assigns TRUE or FALSE. In this case, FALSE as 2 is greater than 1\nlog\n\n[1] FALSE\n\nclass(log)\n\n[1] \"logical\"\n\n\n\n\n3.6.2 Random Variables\nIn statistics, we differentiate between data to capture:\n\nQualitative attributes categorise objects eg.gender, marital status. To measure these attributes, we use Categorical data which can be divided into:\n\nNominal data in categories that have no inherent order eg. gender\nOrdinal data in categories that have an inherent order eg. income bands\n\nQuantitative attributes:\n\nDiscrete data: count objects of a certain category eg. number of kids, cars\nContinuous data: precise numeric measures eg. weight, income, length.\n\n\nIn R these three types of random variables are represented by the following types of R data object:\n\n\n\n\n\n\n\nvariables\n\n\nobjects\n\n\n\n\n\n\nnominal\n\n\nfactor\n\n\n\n\nordinal\n\n\nordered factor\n\n\n\n\ndiscrete\n\n\nnumeric\n\n\n\n\ncontinuous\n\n\nnumeric\n\n\n\n\n\nSurvey and R data types\n\n\nWe have already encountered the R data type numeric. The next section introduces the factor data type.\n\n3.6.2.1 Factor\nWhat is a factor?\nA factor variable assigns a numeric code to each possible category (level) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of 10,000 males and females to a list of 10,000 1s and 0s. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes.\nFor example, the variable gender, converted to a factor, would be stored as a series of 1s and 2s, where 1 = female and 2 = male; but would be displayed in all outputs using their category labels of female and male.\nCreating a factor\nTo convert a numeric or character vector into a factor use the factor( ) function. For instance:\n\ngender <- c(\"female\",\"male\",\"male\",\"female\",\"female\") # create a gender variable\ngender <- factor(gender) # replace character vector with a factor version\ngender\n\n[1] female male male female female\nLevels: female male\n\nclass(gender)\n\n[1] \"factor\"\n\nstr(gender)\n\n Factor w/ 2 levels \"female\",\"male\": 1 2 2 1 1\n\n\nNow gender is a factor and is stored as a series of 1s and 2s, with 1s representing females and 2s representing males. The function levels( ) lists the levels (categories) associated with a given factor variable:\n\nlevels(gender)\n\n[1] \"female\" \"male\" \n\n\nThe categories are reported in the order that they have been numbered (starting from 1). Hence from the output we can infer that females are coded as 1, and males as 2." + "section": "\n3.6 Variables and objects", + "text": "3.6 Variables and objects\nAn object is a data structure having attributes and methods. In fact, everything in R is an object!\nA variable is a type of data object. Data objects also include list, vector, matrices and text.\n\nCreating a data object\n\nIn R a variable can be created by using the symbol <- to assign a value to a variable name. The variable name is entered on the left <- and the value on the right. Note: Data objects can be given any name, provided that they start with a letter of the alphabet, and include only letters of the alphabet, numbers and the characters . and _. Hence AgeGroup, Age_Group and Age.Group are all valid names for an R data object. Note also that R is case-sensitive, to agegroup and AgeGroup would be treated as different data objects.\nTo save the value 28 to a variable (data object) labelled age, run the code:\n\nage <- 28\n\n\nInspecting a data object\n\nTo inspect the contents of the data object age run the following line of code:\n\nage\n\n[1] 28\n\n\nFind out what kind (class) of data object age is using:\n\nclass(age) \n\n[1] \"numeric\"\n\n\nInspect the structure of the age data object:\n\nstr(age) \n\n num 28\n\n\n\nThe vector data object\n\nWhat if we have more than one response? We can use the c( ) function to combine multiple values into one data vector object:\n\nage <- c(28, 36, 25, 24, 32)\nage\n\n[1] 28 36 25 24 32\n\nclass(age) #Still numeric..\n\n[1] \"numeric\"\n\nstr(age) #..but now a vector (set) of 5 separate values\n\n num [1:5] 28 36 25 24 32\n\n\nNote that on each line in the code above any text following the # character is ignored by R when executing the code. Instead, text following a # can be used to add comments to the code to make clear what the code is doing. Two marks of good code are a clear layout and clear commentary on the code.\n\n3.6.1 Basic Data Types\nThere are a number of data types. Four are the most common. In R, numeric is the default type for numbers. It stores all numbers as floating-point numbers (numbers with decimals). This is because most statistical calculations deal with numbers with up to two decimals.\n\nNumeric\n\n\nnum <- 4.5 # Decimal values\nclass(num)\n\n[1] \"numeric\"\n\n\n\nInteger\n\n\nint <- as.integer(4) # Natural numbers. Note integers are also numerics.\nclass(int)\n\n[1] \"integer\"\n\n\n\nCharacter\n\n\ncha <- \"are you enjoying this?\" # text or string. You can also type `as.character(\"are you enjoying this?\")`\nclass(cha)\n\n[1] \"character\"\n\n\n\nLogical\n\n\nlog <- 2 < 1 # assigns TRUE or FALSE. In this case, FALSE as 2 is greater than 1\nlog\n\n[1] FALSE\n\nclass(log)\n\n[1] \"logical\"\n\n\n\n3.6.2 Random Variables\nIn statistics, we differentiate between data to capture:\n\n\nQualitative attributes categorise objects eg.gender, marital status. To measure these attributes, we use Categorical data which can be divided into:\n\n\nNominal data in categories that have no inherent order eg. gender\n\nOrdinal data in categories that have an inherent order eg. income bands\n\n\n\nQuantitative attributes:\n\n\nDiscrete data: count objects of a certain category eg. number of kids, cars\n\nContinuous data: precise numeric measures eg. weight, income, length.\n\n\n\nIn R these three types of random variables are represented by the following types of R data object:\n\n\n\n\nvariables\nobjects\n\n\n\nnominal\nfactor\n\n\nordinal\nordered factor\n\n\ndiscrete\nnumeric\n\n\ncontinuous\nnumeric\n\n\n\n\n\nWe have already encountered the R data type numeric. The next section introduces the factor data type.\n\n3.6.2.1 Factor\nWhat is a factor?\nA factor variable assigns a numeric code to each possible category (level) in a variable. Behind the scenes, R stores the variable using these numeric codes to save space and speed up computing. For example, compare the size of a list of 10,000 males and females to a list of 10,000 1s and 0s. At the same time R also saves the category names associated with each numeric code (level). These are used for display purposes.\nFor example, the variable gender, converted to a factor, would be stored as a series of 1s and 2s, where 1 = female and 2 = male; but would be displayed in all outputs using their category labels of female and male.\nCreating a factor\nTo convert a numeric or character vector into a factor use the factor( ) function. For instance:\n\ngender <- c(\"female\",\"male\",\"male\",\"female\",\"female\") # create a gender variable\ngender <- factor(gender) # replace character vector with a factor version\ngender\n\n[1] female male male female female\nLevels: female male\n\nclass(gender)\n\n[1] \"factor\"\n\nstr(gender)\n\n Factor w/ 2 levels \"female\",\"male\": 1 2 2 1 1\n\n\nNow gender is a factor and is stored as a series of 1s and 2s, with 1s representing females and 2s representing males. The function levels( ) lists the levels (categories) associated with a given factor variable:\n\nlevels(gender)\n\n[1] \"female\" \"male\" \n\n\nThe categories are reported in the order that they have been numbered (starting from 1). Hence from the output we can infer that females are coded as 1, and males as 2.", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#data-frames", "href": "03-data-wrangling.html#data-frames", "title": "3  Data Wrangling", - "section": "3.7 Data Frames", - "text": "3.7 Data Frames\nR stores different types of data using different types of data structure. Data are normally stored as a data.frame. A data frames contain one row per observation (e.g. wards) and one column per attribute (eg. population and health).\nWe create three variables wards, population (pop) and people with good health (ghealth). We use 2011 census data counts for total population and good health for wards in Liverpool.\n\nwards <- c(\"Allerton and Hunts Cross\",\"Anfield\",\"Belle Vale\",\"Central\",\"Childwall\",\"Church\",\"Clubmoor\",\"County\",\"Cressington\",\"Croxteth\",\"Everton\",\"Fazakerley\",\"Greenbank\",\"Kensington and Fairfield\",\"Kirkdale\",\"Knotty Ash\",\"Mossley Hill\",\"Norris Green\",\"Old Swan\",\"Picton\",\"Princes Park\",\"Riverside\",\"St Michael's\",\"Speke-Garston\",\"Tuebrook and Stoneycroft\",\"Warbreck\",\"Wavertree\",\"West Derby\",\"Woolton\",\"Yew Tree\")\n\npop <- c(14853,14510,15004,20340,13908,13974,15272,14045,14503,\n 14561,14782,16786,16132,15377,16115,13312,13816,15047,\n 16461,17009,17104,18422,12991,20300,16489,16481,14772,\n 14382,12921,16746)\n\nghealth <- c(7274,6124,6129,11925,7219,7461,6403,5930,7094,6992,\n 5517,7879,8990,6495,6662,5981,7322,6529,7192,7953,\n 7636,9001,6450,8973,7302,7521,7268,7013,6025,7717)\n\nNote that pop and ghealth and wards contains characters.\n\n3.7.1 Creating A Data Frame\nWe can create a data frame and examine its structure:\n\ndf <- data.frame(wards, pop, ghealth)\ndf # or use view(data)\n\n wards pop ghealth\n1 Allerton and Hunts Cross 14853 7274\n2 Anfield 14510 6124\n3 Belle Vale 15004 6129\n4 Central 20340 11925\n5 Childwall 13908 7219\n6 Church 13974 7461\n7 Clubmoor 15272 6403\n8 County 14045 5930\n9 Cressington 14503 7094\n10 Croxteth 14561 6992\n11 Everton 14782 5517\n12 Fazakerley 16786 7879\n13 Greenbank 16132 8990\n14 Kensington and Fairfield 15377 6495\n15 Kirkdale 16115 6662\n16 Knotty Ash 13312 5981\n17 Mossley Hill 13816 7322\n18 Norris Green 15047 6529\n19 Old Swan 16461 7192\n20 Picton 17009 7953\n21 Princes Park 17104 7636\n22 Riverside 18422 9001\n23 St Michael's 12991 6450\n24 Speke-Garston 20300 8973\n25 Tuebrook and Stoneycroft 16489 7302\n26 Warbreck 16481 7521\n27 Wavertree 14772 7268\n28 West Derby 14382 7013\n29 Woolton 12921 6025\n30 Yew Tree 16746 7717\n\nstr(df) # or use glimpse(data) \n\n'data.frame': 30 obs. of 3 variables:\n $ wards : chr \"Allerton and Hunts Cross\" \"Anfield\" \"Belle Vale\" \"Central\" ...\n $ pop : num 14853 14510 15004 20340 13908 ...\n $ ghealth: num 7274 6124 6129 11925 7219 ...\n\n\n\n\n3.7.2 Referencing Data Frames\nThroughout this module, you will need to refer to particular parts of a dataframe - perhaps a particular column (an area attribute); or a particular subset of respondents. Hence it is worth spending some time now mastering this particular skill.\nThe relevant R function, [ ], has the format [row,col] or, more generally, [set of rows, set of cols].\nRun the following commands to get a feel of how to extract different slices of the data:\n\ndf # whole data.frame\ndf[1, 1] # contents of first row and column\ndf[2, 2:3] # contents of the second row, second and third columns\ndf[1, ] # first row, ALL columns [the default if no columns specified]\ndf[ ,1:2] # ALL rows; first and second columns\ndf[c(1,3,5), ] # rows 1,3,5; ALL columns\ndf[ , 2] # ALL rows; second column (by default results containing only \n #one column are converted back into a vector)\ndf[ , 2, drop=FALSE] # ALL rows; second column (returned as a data.frame)\n\nIn the above, note that we have used two other R functions:\n\n1:3 The colon operator tells R to produce a list of numbers including the named start and end points.\nc(1,3,5) Tells R to combine the contents within the brackets into one list of objects\n\nRun both of these fuctions on their own to get a better understanding of what they do.\nThree other methods for referencing the contents of a data.frame make direct use of the variable names within the data.frame, which tends to make for easier to read/understand code:\n\ndf[,\"pop\"] # variable name in quotes inside the square brackets\ndf$pop # variable name prefixed with $ and appended to the data.frame name\n# or you can use attach\nattach(df)\npop # but be careful if you already have an age variable in your local workspace\n\nWant to check the variables available, use the names( ):\n\nnames(df)\n\n[1] \"wards\" \"pop\" \"ghealth\"" + "section": "\n3.7 Data Frames", + "text": "3.7 Data Frames\nR stores different types of data using different types of data structure. Data are normally stored as a data.frame. A data frames contain one row per observation (e.g. wards) and one column per attribute (eg. population and health).\nWe create three variables wards, population (pop) and people with good health (ghealth). We use 2011 census data counts for total population and good health for wards in Liverpool.\n\nwards <- c(\"Allerton and Hunts Cross\",\"Anfield\",\"Belle Vale\",\"Central\",\"Childwall\",\"Church\",\"Clubmoor\",\"County\",\"Cressington\",\"Croxteth\",\"Everton\",\"Fazakerley\",\"Greenbank\",\"Kensington and Fairfield\",\"Kirkdale\",\"Knotty Ash\",\"Mossley Hill\",\"Norris Green\",\"Old Swan\",\"Picton\",\"Princes Park\",\"Riverside\",\"St Michael's\",\"Speke-Garston\",\"Tuebrook and Stoneycroft\",\"Warbreck\",\"Wavertree\",\"West Derby\",\"Woolton\",\"Yew Tree\")\n\npop <- c(14853,14510,15004,20340,13908,13974,15272,14045,14503,\n 14561,14782,16786,16132,15377,16115,13312,13816,15047,\n 16461,17009,17104,18422,12991,20300,16489,16481,14772,\n 14382,12921,16746)\n\nghealth <- c(7274,6124,6129,11925,7219,7461,6403,5930,7094,6992,\n 5517,7879,8990,6495,6662,5981,7322,6529,7192,7953,\n 7636,9001,6450,8973,7302,7521,7268,7013,6025,7717)\n\nNote that pop and ghealth and wards contains characters.\n\n3.7.1 Creating A Data Frame\nWe can create a data frame and examine its structure:\n\ndf <- data.frame(wards, pop, ghealth)\ndf # or use view(data)\n\n wards pop ghealth\n1 Allerton and Hunts Cross 14853 7274\n2 Anfield 14510 6124\n3 Belle Vale 15004 6129\n4 Central 20340 11925\n5 Childwall 13908 7219\n6 Church 13974 7461\n7 Clubmoor 15272 6403\n8 County 14045 5930\n9 Cressington 14503 7094\n10 Croxteth 14561 6992\n11 Everton 14782 5517\n12 Fazakerley 16786 7879\n13 Greenbank 16132 8990\n14 Kensington and Fairfield 15377 6495\n15 Kirkdale 16115 6662\n16 Knotty Ash 13312 5981\n17 Mossley Hill 13816 7322\n18 Norris Green 15047 6529\n19 Old Swan 16461 7192\n20 Picton 17009 7953\n21 Princes Park 17104 7636\n22 Riverside 18422 9001\n23 St Michael's 12991 6450\n24 Speke-Garston 20300 8973\n25 Tuebrook and Stoneycroft 16489 7302\n26 Warbreck 16481 7521\n27 Wavertree 14772 7268\n28 West Derby 14382 7013\n29 Woolton 12921 6025\n30 Yew Tree 16746 7717\n\nstr(df) # or use glimpse(data) \n\n'data.frame': 30 obs. of 3 variables:\n $ wards : chr \"Allerton and Hunts Cross\" \"Anfield\" \"Belle Vale\" \"Central\" ...\n $ pop : num 14853 14510 15004 20340 13908 ...\n $ ghealth: num 7274 6124 6129 11925 7219 ...\n\n\n\n3.7.2 Referencing Data Frames\nTo refer to particular parts of a dataframe - say, a particular column (an area attribute), or a subset of respondents. Hence it is worth spending some time understanding how to reference dataframes.\nThe relevant R function, [ ], has the format [row,col] or, more generally, [set of rows, set of cols].\nRun the following commands to get a feel of how to extract different slices of the data:\n\ndf # whole data.frame\ndf[1, 1] # contents of first row and column\ndf[2, 2:3] # contents of the second row, second and third columns\ndf[1, ] # first row, ALL columns [the default if no columns specified]\ndf[ ,1:2] # ALL rows; first and second columns\ndf[c(1,3,5), ] # rows 1,3,5; ALL columns\ndf[ , 2] # ALL rows; second column (by default results containing only \n #one column are converted back into a vector)\ndf[ , 2, drop=FALSE] # ALL rows; second column (returned as a data.frame)\n\nIn the above, note that we have used two other R functions:\n\n1:3 The colon operator tells R to produce a list of numbers including the named start and end points.\nc(1,3,5) Tells R to combine the contents within the brackets into one list of objects\n\nRun both of these fuctions on their own to get a better understanding of what they do.\nThree other methods for referencing the contents of a data.frame make direct use of the variable names within the data.frame, which tends to make for easier to read/understand code:\n\ndf[,\"pop\"] # variable name in quotes inside the square brackets\ndf$pop # variable name prefixed with $ and appended to the data.frame name\n# or you can use attach\nattach(df)\npop # but be careful if you already have an age variable in your local workspace\n\nWant to check the variables available, use the names( ):\n\nnames(df)\n\n[1] \"wards\" \"pop\" \"ghealth\"", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#sec_readdata", "href": "03-data-wrangling.html#sec_readdata", "title": "3  Data Wrangling", - "section": "3.8 Read Data", - "text": "3.8 Read Data\nEnsure your memory is clear\n\nrm(list=ls()) # rm for targeted deletion / ls for listing all existing objects\n\nThere are many commands to read / load data onto R. The command to use will depend upon the format they have been saved. Normally they are saved in csv format from Excel or other software packages. So we use either:\n\ndf <- read.table(\"path/file_name.csv\", header = FALSE, sep =\",\")\ndf <- read(\"path/file_name.csv\", header = FALSE)\ndf <- read.csv2(\"path/file_name.csv\", header = FALSE)\n\nTo read files in other formats, refer to this useful DataCamp tutorial\n\ncensus <- read.csv(\"data/census/census_data.csv\")\nhead(census)\n\n code ward pop16_74 higher_managerial pop ghealth\n1 E05000886 Allerton and Hunts Cross 10930 1103 14853 7274\n2 E05000887 Anfield 10712 312 14510 6124\n3 E05000888 Belle Vale 10987 432 15004 6129\n4 E05000889 Central 19174 1346 20340 11925\n5 E05000890 Childwall 10410 1123 13908 7219\n6 E05000891 Church 10569 1843 13974 7461\n\n# NOTE: always ensure your are setting the correct directory leading to the data. \n# It may differ from your existing working directory\n\n\n3.8.1 Quickly inspect the data\n\nWhat class?\nWhat R data types?\nWhat data types?\n\n\n# 1\nclass(census)\n# 2 & 3\nstr(census)\n\nJust interested in the variable names:\n\nnames(census)\n\n[1] \"code\" \"ward\" \"pop16_74\" \n[4] \"higher_managerial\" \"pop\" \"ghealth\" \n\n\nor want to view the data:\nView(census)" + "section": "\n3.8 Read Data", + "text": "3.8 Read Data\nEnsure your memory is clear\n\nrm(list=ls()) # rm for targeted deletion / ls for listing all existing objects\n\n\n\n\n\n\n\n\n\nNote\n\n\n\nWhen opening a file, ensure the correct directory set up pointing to your data. It may differ from your existing working directory.\n\n\nThere are many commands to read / load data onto R. The command to use will depend upon the format they have been saved. Normally they are saved in csv format from Excel or other software packages. So we use either:\n\ndf <- read.table(\"path/file_name.csv\", header = FALSE, sep =\",\")\ndf <- read(\"path/file_name.csv\", header = FALSE)\ndf <- read.csv2(\"path/file_name.csv\", header = FALSE)\n\nTo read files in other formats, refer to this useful DataCamp tutorial\n\ncensus <- read.csv(\"data/census/census_data.csv\")\nhead(census)\n\n code ward pop16_74 higher_managerial pop ghealth\n1 E05000886 Allerton and Hunts Cross 10930 1103 14853 7274\n2 E05000887 Anfield 10712 312 14510 6124\n3 E05000888 Belle Vale 10987 432 15004 6129\n4 E05000889 Central 19174 1346 20340 11925\n5 E05000890 Childwall 10410 1123 13908 7219\n6 E05000891 Church 10569 1843 13974 7461\n\n\n\n3.8.1 Quickly inspect the data\nUsing the following questions to lead the inspection: What class? What R data types? What data types?\n\n# 1\nclass(census)\n# 2 & 3\nstr(census)\n\nJust interested in the variable names:\n\nnames(census)\n\n[1] \"code\" \"ward\" \"pop16_74\" \n[4] \"higher_managerial\" \"pop\" \"ghealth\" \n\n\nor want to view the data:\nView(census)", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#manipulation-data", "href": "03-data-wrangling.html#manipulation-data", "title": "3  Data Wrangling", - "section": "3.9 Manipulation Data", - "text": "3.9 Manipulation Data\n\n3.9.1 Adding New Variables\nUsually you want to add / create new variables to your data frame using existing variables eg. computing percentages by dividing two variables. There are many ways in which you can do this i.e. referecing a data frame as we have done above, or using $ (e.g. census$pop). For this module, we’ll use tidyverse:\n\ncensus <- census %>% mutate(per_ghealth = ghealth / pop)\n\nNote we used a pipe operator %>%, which helps make the code more efficient and readable - more details, see Grolemund and Wickham (2019). When using the pipe operator, recall to first indicate the data frame before %>%.\nNote also the use a variable name before the = sign in brackets to indicate the name of the new variable after mutate.\n\n\n3.9.2 Selecting Variables\nUsually you want to select a subset of variables for your analysis as storing to large data sets in your R memory can reduce the processing speed of your machine. A selection of data can be achieved by using the select function:\n\nndf <- census %>% select(ward, pop16_74, per_ghealth)\n\nAgain first indicate the data frame and then the variable you want to select to build a new data frame. Note the code chunk above has created a new data frame called ndf. Explore it.\n\n\n3.9.3 Filtering Data\nYou may also want to filter values based on defined conditions. You may want to filter observations greater than a certain threshold or only areas within a certain region. For example, you may want to select areas with a percentage of good health population over 50%:\n\nndf2 <- census %>% filter(per_ghealth < 0.5)\n\nYou can use more than one variables to set conditions. Use “,” to add a condition.\n\n\n3.9.4 Joining Data Drames\nWhen working with spatial data, we often need to join data. To this end, you need a common unique id variable. Let’s say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (ie. census_data2.csv).\n\n# read data\ncensus2 <- read.csv(\"data/census/census_data2.csv\")\n# visualise data structure\nstr(census2)\n\n'data.frame': 30 obs. of 3 variables:\n $ geo_code : chr \"E05000886\" \"E05000887\" \"E05000888\" \"E05000889\" ...\n $ households : int 6359 6622 6622 7139 5391 5884 6576 6745 6317 6024 ...\n $ socialrented_households: int 827 1508 2818 1311 374 178 2859 1564 1023 1558 ...\n\n\nThe variable geo_code in this data frame corresponds to the code in the existing data frame and they are unique so they can be automatically matched by using the merge() function. The merge() function uses two arguments: x and y. The former refers to data frame 1 and the latter to data frame 2. Both of these two data frames must have a id variable containing the same information. Note they can have different names. Another key argument to include is all.x=TRUE which tells the function to keep all the records in x, but only those in y that match in case there are discrepancies in the id variable.\n\n# join data frames\njoin_dfs <- merge(census, census2, by.x=\"code\", by.y=\"geo_code\", all.x = TRUE)\n# check data\nhead(join_dfs)\n\n code ward pop16_74 higher_managerial pop ghealth\n1 E05000886 Allerton and Hunts Cross 10930 1103 14853 7274\n2 E05000887 Anfield 10712 312 14510 6124\n3 E05000888 Belle Vale 10987 432 15004 6129\n4 E05000889 Central 19174 1346 20340 11925\n5 E05000890 Childwall 10410 1123 13908 7219\n6 E05000891 Church 10569 1843 13974 7461\n per_ghealth households socialrented_households\n1 0.4897327 6359 827\n2 0.4220538 6622 1508\n3 0.4084911 6622 2818\n4 0.5862832 7139 1311\n5 0.5190538 5391 374\n6 0.5339201 5884 178\n\n\n\n\n3.9.5 Saving Data\nIt may also be convinient to save your R projects. They contains all the objects that you have created in your workspace by using the save.image( ) function:\n\nsave.image(\"week1_envs453.RData\")\n\nThis creates a file labelled “week1_envs453.RData” in your working directory. You can load this at a later stage using the load( ) function.\n\nload(\"week1_envs453.RData\")\n\nAlternatively you can save / export your data into a csv file. The first argument in the function is the object name, and the second: the name of the csv we want to create.\n\nwrite.csv(join_dfs, \"join_censusdfs.csv\")" + "section": "\n3.9 Manipulation Data", + "text": "3.9 Manipulation Data\n\n3.9.1 Adding New Variables\nUsually you want to add / create new variables to your data frame using existing variables eg. computing percentages by dividing two variables. There are many ways in which you can do this i.e. referecing a data frame as we have done above, or using $ (e.g. census$pop). For this module, we’ll use tidyverse:\n\ncensus <- census %>% \n mutate( per_ghealth = ghealth / pop )\n\nNote we used a pipe operator %>%, which helps make the code more efficient and readable - more details, see Grolemund and Wickham (2019). When using the pipe operator, recall to first indicate the data frame before %>%.\nNote also the use a variable name before the = sign in brackets to indicate the name of the new variable after mutate.\n\n3.9.2 Selecting Variables\nUsually you want to select a subset of variables for your analysis as storing to large data sets in your R memory can reduce the processing speed of your machine. A selection of data can be achieved by using the select function:\n\nndf <- census %>% \n select( ward, pop16_74, per_ghealth )\n\nAgain first indicate the data frame and then the variable you want to select to build a new data frame. Note the code chunk above has created a new data frame called ndf. Explore it.\n\n3.9.3 Filtering Data\nYou may also want to filter values based on defined conditions. You may want to filter observations greater than a certain threshold or only areas within a certain region. For example, you may want to select areas with a percentage of good health population over 50%:\n\nndf2 <- census %>% \n filter( per_ghealth < 0.5 )\n\nYou can use more than one variables to set conditions. Use “,” to add a condition.\n\n3.9.4 Joining Data Drames\nWhen working with spatial data, we often need to join data. To this end, you need a common unique id variable. Let’s say, we want to add a data frame containing census data on households for Liverpool, and join the new attributes to one of the existing data frames in the workspace. First we will read the data frame we want to join (i.e. census_data2.csv).\n\n# read data\ncensus2 <- read.csv(\"data/census/census_data2.csv\")\n# visualise data structure\nstr(census2)\n\n'data.frame': 30 obs. of 3 variables:\n $ geo_code : chr \"E05000886\" \"E05000887\" \"E05000888\" \"E05000889\" ...\n $ households : int 6359 6622 6622 7139 5391 5884 6576 6745 6317 6024 ...\n $ socialrented_households: int 827 1508 2818 1311 374 178 2859 1564 1023 1558 ...\n\n\nThe variable geo_code in this data frame corresponds to the code in the existing data frame and they are unique so they can be automatically matched by using the merge() function. The merge() function uses two arguments: x and y. The former refers to data frame 1 and the latter to data frame 2. Both of these two data frames must have a id variable containing the same information. Note they can have different names. Another key argument to include is all.x=TRUE which tells the function to keep all the records in x, but only those in y that match in case there are discrepancies in the id variable.\n\n# join data frames\njoin_dfs <- merge( census, # df1\n census2, # df2\n by.x=\"code\", by.y=\"geo_code\", # common ids\n all.x = TRUE)\n# check data\nhead(join_dfs)\n\n code ward pop16_74 higher_managerial pop ghealth\n1 E05000886 Allerton and Hunts Cross 10930 1103 14853 7274\n2 E05000887 Anfield 10712 312 14510 6124\n3 E05000888 Belle Vale 10987 432 15004 6129\n4 E05000889 Central 19174 1346 20340 11925\n5 E05000890 Childwall 10410 1123 13908 7219\n6 E05000891 Church 10569 1843 13974 7461\n per_ghealth households socialrented_households\n1 0.4897327 6359 827\n2 0.4220538 6622 1508\n3 0.4084911 6622 2818\n4 0.5862832 7139 1311\n5 0.5190538 5391 374\n6 0.5339201 5884 178\n\n\n\n3.9.5 Saving Data\nIt may also be convenient to save your R projects. They contains all the objects that you have created in your workspace by using the save.image( ) function:\n\nsave.image(\"week1_envs453.RData\")\n\nThis creates a file labelled “week1_envs453.RData” in your working directory. You can load this at a later stage using the load( ) function.\n\nload(\"week1_envs453.RData\")\n\nAlternatively you can save / export your data into a csv file. The first argument in the function is the object name, and the second: the name of the csv we want to create.\n\nwrite.csv(join_dfs, \"join_censusdfs.csv\")", + "crumbs": [ + "3  Data Wrangling" + ] }, { "objectID": "03-data-wrangling.html#using-spatial-data-frames", "href": "03-data-wrangling.html#using-spatial-data-frames", "title": "3  Data Wrangling", - "section": "3.10 Using Spatial Data Frames", - "text": "3.10 Using Spatial Data Frames\nA core area of this module is learning to work with spatial data in R. R has various purposedly designed packages for manipulation of spatial data and spatial analysis techniques. Various R packages exist in CRAN eg. spatial, sgeostat, splancs, maptools, tmap, rgdal, spand and more recent development of sf - see Lovelace, Nowosad, and Muenchow (2019) for a great description and historical context for some of these packages.\nDuring this session, we will use sf.\nWe first need to import our spatial data. We will use a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data.\n\n3.10.1 Read Spatial Data\n\noa_shp <- st_read(\"data/census/Liverpool_OA.shp\")\n\nReading layer `Liverpool_OA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/census/Liverpool_OA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 1584 features and 18 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 332390.2 ymin: 379748.5 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n\nExamine the input data. A spatial data frame stores a range of attributes derived from a shapefile including the geometry of features (e.g. polygon shape and location), attributes for each feature (stored in the .dbf), projection and coordinates of the shapefile’s bounding box - for details, execute:\n\n?st_read\n\nYou can employ the usual functions to visualise the content of the created data frame:\n\n# visualise variable names\nnames(oa_shp)\n\n [1] \"OA_CD\" \"LSOA_CD\" \"MSOA_CD\" \"LAD_CD\" \"pop\" \"H_Vbad\" \n [7] \"H_bad\" \"H_fair\" \"H_good\" \"H_Vgood\" \"age_men\" \"age_med\" \n[13] \"age_60\" \"S_Rent\" \"Ethnic\" \"illness\" \"unemp\" \"males\" \n[19] \"geometry\"\n\n# data structure\nstr(oa_shp)\n\nClasses 'sf' and 'data.frame': 1584 obs. of 19 variables:\n $ OA_CD : chr \"E00176737\" \"E00033515\" \"E00033141\" \"E00176757\" ...\n $ LSOA_CD : chr \"E01033761\" \"E01006614\" \"E01006546\" \"E01006646\" ...\n $ MSOA_CD : chr \"E02006932\" \"E02001358\" \"E02001365\" \"E02001369\" ...\n $ LAD_CD : chr \"E08000012\" \"E08000012\" \"E08000012\" \"E08000012\" ...\n $ pop : int 185 281 208 200 321 187 395 320 316 214 ...\n $ H_Vbad : int 1 2 3 7 4 4 5 9 5 4 ...\n $ H_bad : int 2 20 10 8 10 25 19 22 25 17 ...\n $ H_fair : int 9 47 22 17 32 70 42 53 55 39 ...\n $ H_good : int 53 111 71 52 112 57 131 104 104 53 ...\n $ H_Vgood : int 120 101 102 116 163 31 198 132 127 101 ...\n $ age_men : num 27.9 37.7 37.1 33.7 34.2 ...\n $ age_med : num 25 36 32 29 34 53 23 30 34 29 ...\n $ age_60 : num 0.0108 0.1637 0.1971 0.1 0.1402 ...\n $ S_Rent : num 0.0526 0.176 0.0235 0.2222 0.0222 ...\n $ Ethnic : num 0.3514 0.0463 0.0192 0.215 0.0779 ...\n $ illness : int 185 281 208 200 321 187 395 320 316 214 ...\n $ unemp : num 0.0438 0.121 0.1121 0.036 0.0743 ...\n $ males : int 122 128 95 120 158 123 207 164 157 94 ...\n $ geometry:sfc_MULTIPOLYGON of length 1584; first list element: List of 1\n ..$ :List of 1\n .. ..$ : num [1:14, 1:2] 335106 335130 335164 335173 335185 ...\n ..- attr(*, \"class\")= chr [1:3] \"XY\" \"MULTIPOLYGON\" \"sfg\"\n - attr(*, \"sf_column\")= chr \"geometry\"\n - attr(*, \"agr\")= Factor w/ 3 levels \"constant\",\"aggregate\",..: NA NA NA NA NA NA NA NA NA NA ...\n ..- attr(*, \"names\")= chr [1:18] \"OA_CD\" \"LSOA_CD\" \"MSOA_CD\" \"LAD_CD\" ...\n\n# see first few observations\nhead(oa_shp)\n\nSimple feature collection with 6 features and 18 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335071.6 ymin: 389876.7 xmax: 339426.9 ymax: 394479\nProjected CRS: Transverse_Mercator\n OA_CD LSOA_CD MSOA_CD LAD_CD pop H_Vbad H_bad H_fair H_good\n1 E00176737 E01033761 E02006932 E08000012 185 1 2 9 53\n2 E00033515 E01006614 E02001358 E08000012 281 2 20 47 111\n3 E00033141 E01006546 E02001365 E08000012 208 3 10 22 71\n4 E00176757 E01006646 E02001369 E08000012 200 7 8 17 52\n5 E00034050 E01006712 E02001375 E08000012 321 4 10 32 112\n6 E00034280 E01006761 E02001366 E08000012 187 4 25 70 57\n H_Vgood age_men age_med age_60 S_Rent Ethnic illness unemp\n1 120 27.94054 25 0.01081081 0.05263158 0.35135135 185 0.04379562\n2 101 37.71174 36 0.16370107 0.17600000 0.04626335 281 0.12101911\n3 102 37.08173 32 0.19711538 0.02352941 0.01923077 208 0.11214953\n4 116 33.73000 29 0.10000000 0.22222222 0.21500000 200 0.03597122\n5 163 34.19003 34 0.14018692 0.02222222 0.07788162 321 0.07428571\n6 31 56.09091 53 0.44919786 0.88524590 0.11764706 187 0.44615385\n males geometry\n1 122 MULTIPOLYGON (((335106.3 38...\n2 128 MULTIPOLYGON (((335810.5 39...\n3 95 MULTIPOLYGON (((336738 3931...\n4 120 MULTIPOLYGON (((335914.5 39...\n5 158 MULTIPOLYGON (((339325 3914...\n6 123 MULTIPOLYGON (((338198.1 39...\n\n\nTASK:\n\nWhat are the geographical hierarchy in these data?\nWhat is the smallest geography?\nWhat is the largest geography?\n\n\n\n3.10.2 Basic Mapping\nAgain, many functions exist in CRAN for creating maps:\n\nplot to create static maps\ntmap to create static and interactive maps\nleaflet to create interactive maps\nmapview to create interactive maps\nggplot2 to create data visualisations, including static maps\nshiny to create web applications, including maps\n\nHere this notebook demonstrates the use of plot and tmap. First plot is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right,\n\n3.10.2.1 Using plot\n\n# mapping geometry\nplot(st_geometry(oa_shp))\n\n\n\n\nOAs of Livepool\n\n\n\n\nand then:\n\n# map attributes, adding intervals\nplot(oa_shp[\"Ethnic\"], key.pos = 4, axes = TRUE, key.width = lcm(1.3), key.length = 1.,\n breaks = \"jenks\", lwd = 0.1, border = 'grey') \n\n\n\n\nSpatial distribution of ethnic groups, Liverpool\n\n\n\n\nTASK:\n\nWhat is the key pattern emerging from this map?\n\n\n\n3.10.2.2 Using tmap\nSimilar to ggplot2, tmap is based on the idea of a ‘grammar of graphics’ which involves a separation between the input data and aesthetics (i.e. the way data are visualised). Each data set can be mapped in various different ways, including location as defined by its geometry, colour and other features. The basic building block is tm_shape() (which defines input data), followed by one or more layer elements such as tm_fill() and tm_dots().\n\n# ensure geometry is valid\noa_shp = sf::st_make_valid(oa_shp)\n\n# map\nlegend_title = expression(\"% ethnic pop.\")\nmap_oa = tm_shape(oa_shp) +\n tm_fill(col = \"Ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .01) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) # add scale bar\nmap_oa\n\n\n\n\nNote that the operation + is used to add new layers. You can set style themes by tm_style. To visualise the existing styles use tmap_style_catalogue(), and you can also evaluate the code chunk below if you would like to create an interactive map.\n\ntmap_mode(\"view\")\nmap_oa\n\nTASK:\n\nTry mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate?\n\n\n\n\n3.10.3 Comparing geographies\nIf you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see lecture notes. To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together.\n\n# read data at the msoa level\nmsoa_shp <- st_read(\"data/census/Liverpool_MSOA.shp\")\n\nReading layer `Liverpool_MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/census/Liverpool_MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 16 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"Ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \n\n# arrange maps \ntmap_arrange(map_msoa, map_oa) \n\n\n\n\nTASK:\n\nWhat differences do you see between OAs and MSOAs?\nCan you identify areas of spatial clustering? Where are they?" + "section": "\n3.10 Using Spatial Data Frames", + "text": "3.10 Using Spatial Data Frames\nA core area of the module is learning to work with spatial data in R. R has various purposedly designed packages for manipulation of spatial data and spatial analysis techniques. Various packages exist in CRAN, including sf (Pebesma 2018, 2022a), stars (Pebesma 2022b), terra, s2 (Dunnington, Pebesma, and Rubak 2023), lwgeom (Pebesma 2023), gstat (Pebesma 2004; Pebesma and Graeler 2022), spdep (Bivand 2022), spatialreg (Bivand and Piras 2022), spatstat (Baddeley, Rubak, and Turner 2015; Baddeley, Turner, and Rubak 2022), tmap (Tennekes 2018, 2022), mapview (Appelhans et al. 2022) and more. A key package is this ecosystem is sf (Pebesma and Bivand 2023). R package sf provides a table format for simple features, where feature geometries are stored in a list-column. It appeared in 2016 and was developed to move spatial data analysis in R closer to standards-based approaches seen in the industry and open source projects, to build upon more modern versions of open source geospatial software stack and allow for integration of R spatial software with the tidyverse (Wickham et al. 2019), particularly ggplot2, dplyr, and tidyr. Hence, this book relies heavely on sf for the manipulation and analysis of the data.\n\n\n\n\n\n\n\n\nNote\n\n\n\nLovelace, Nowosad, and Muenchow (2024) provide a helpful overview and evolution of R spatial package ecosystem.\n\n\nTo read our spatial data, we use the st_read function. We read a shapefile containing data at Output Area (OA) level for Liverpool. These data illustrates the hierarchical structure of spatial data.\n\n3.10.1 Read Spatial Data\n\noa_shp <- st_read(\"data/census/Liverpool_OA.shp\")\n\nReading layer `Liverpool_OA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/census/Liverpool_OA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 1584 features and 18 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 332390.2 ymin: 379748.5 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n\nExamine the input data. A spatial data frame stores a range of attributes derived from a shapefile including the geometry of features (e.g. polygon shape and location), attributes for each feature (stored in the .dbf), projection and coordinates of the shapefile’s bounding box - for details, execute:\n\n?st_read\n\nYou can employ the usual functions to visualise the content of the created data frame:\n\n# visualise variable names\nnames(oa_shp)\n\n [1] \"OA_CD\" \"LSOA_CD\" \"MSOA_CD\" \"LAD_CD\" \"pop\" \"H_Vbad\" \n [7] \"H_bad\" \"H_fair\" \"H_good\" \"H_Vgood\" \"age_men\" \"age_med\" \n[13] \"age_60\" \"S_Rent\" \"Ethnic\" \"illness\" \"unemp\" \"males\" \n[19] \"geometry\"\n\n# data structure\nstr(oa_shp)\n\nClasses 'sf' and 'data.frame': 1584 obs. of 19 variables:\n $ OA_CD : chr \"E00176737\" \"E00033515\" \"E00033141\" \"E00176757\" ...\n $ LSOA_CD : chr \"E01033761\" \"E01006614\" \"E01006546\" \"E01006646\" ...\n $ MSOA_CD : chr \"E02006932\" \"E02001358\" \"E02001365\" \"E02001369\" ...\n $ LAD_CD : chr \"E08000012\" \"E08000012\" \"E08000012\" \"E08000012\" ...\n $ pop : int 185 281 208 200 321 187 395 320 316 214 ...\n $ H_Vbad : int 1 2 3 7 4 4 5 9 5 4 ...\n $ H_bad : int 2 20 10 8 10 25 19 22 25 17 ...\n $ H_fair : int 9 47 22 17 32 70 42 53 55 39 ...\n $ H_good : int 53 111 71 52 112 57 131 104 104 53 ...\n $ H_Vgood : int 120 101 102 116 163 31 198 132 127 101 ...\n $ age_men : num 27.9 37.7 37.1 33.7 34.2 ...\n $ age_med : num 25 36 32 29 34 53 23 30 34 29 ...\n $ age_60 : num 0.0108 0.1637 0.1971 0.1 0.1402 ...\n $ S_Rent : num 0.0526 0.176 0.0235 0.2222 0.0222 ...\n $ Ethnic : num 0.3514 0.0463 0.0192 0.215 0.0779 ...\n $ illness : int 185 281 208 200 321 187 395 320 316 214 ...\n $ unemp : num 0.0438 0.121 0.1121 0.036 0.0743 ...\n $ males : int 122 128 95 120 158 123 207 164 157 94 ...\n $ geometry:sfc_MULTIPOLYGON of length 1584; first list element: List of 1\n ..$ :List of 1\n .. ..$ : num [1:14, 1:2] 335106 335130 335164 335173 335185 ...\n ..- attr(*, \"class\")= chr [1:3] \"XY\" \"MULTIPOLYGON\" \"sfg\"\n - attr(*, \"sf_column\")= chr \"geometry\"\n - attr(*, \"agr\")= Factor w/ 3 levels \"constant\",\"aggregate\",..: NA NA NA NA NA NA NA NA NA NA ...\n ..- attr(*, \"names\")= chr [1:18] \"OA_CD\" \"LSOA_CD\" \"MSOA_CD\" \"LAD_CD\" ...\n\n# see first few observations\nhead(oa_shp)\n\nSimple feature collection with 6 features and 18 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335071.6 ymin: 389876.7 xmax: 339426.9 ymax: 394479\nProjected CRS: Transverse_Mercator\n OA_CD LSOA_CD MSOA_CD LAD_CD pop H_Vbad H_bad H_fair H_good\n1 E00176737 E01033761 E02006932 E08000012 185 1 2 9 53\n2 E00033515 E01006614 E02001358 E08000012 281 2 20 47 111\n3 E00033141 E01006546 E02001365 E08000012 208 3 10 22 71\n4 E00176757 E01006646 E02001369 E08000012 200 7 8 17 52\n5 E00034050 E01006712 E02001375 E08000012 321 4 10 32 112\n6 E00034280 E01006761 E02001366 E08000012 187 4 25 70 57\n H_Vgood age_men age_med age_60 S_Rent Ethnic illness unemp\n1 120 27.94054 25 0.01081081 0.05263158 0.35135135 185 0.04379562\n2 101 37.71174 36 0.16370107 0.17600000 0.04626335 281 0.12101911\n3 102 37.08173 32 0.19711538 0.02352941 0.01923077 208 0.11214953\n4 116 33.73000 29 0.10000000 0.22222222 0.21500000 200 0.03597122\n5 163 34.19003 34 0.14018692 0.02222222 0.07788162 321 0.07428571\n6 31 56.09091 53 0.44919786 0.88524590 0.11764706 187 0.44615385\n males geometry\n1 122 MULTIPOLYGON (((335106.3 38...\n2 128 MULTIPOLYGON (((335810.5 39...\n3 95 MULTIPOLYGON (((336738 3931...\n4 120 MULTIPOLYGON (((335914.5 39...\n5 158 MULTIPOLYGON (((339325 3914...\n6 123 MULTIPOLYGON (((338198.1 39...\n\n\n\n\n\n\n\n\n\n\nTask\n\nWhat are the geographical hierarchy in these data?\n\nWhat is the smallest geography?\n\nWhat is the largest geography?\n\n\n\n\n\n\n3.10.2 Basic Mapping\nMany functions exist in CRAN for creating maps:\n\n\nplot to create static maps\n\ntmap to create static and interactive maps\n\nleaflet to create interactive maps\n\nmapview to create interactive maps\n\nggplot2 to create data visualisations, including static maps\n\nshiny to create web applications, including maps\n\nIn this book, we will make use of plot, tmap and ggplot. Normally you use plot to get a quick inspection of the data and tmap and ggplot to get publication quality data visualisations. First plot is used to map the spatial distribution of non-British-born population in Liverpool. First we only map the geometries on the right.\nUsing plot\nWe can use the base plot function to display the boundaries of OAs in Liverpool.\n\n# mapping geometry\nplot(st_geometry(oa_shp))\n\n\n\n\n\n\n\nTo visualise a column in the spatial data frame, we can run:\n\n# map attributes, adding intervals\nplot(oa_shp[\"Ethnic\"], # variable to visualise\n key.pos = 4, \n axes = TRUE, \n key.width = lcm(1.3), \n key.length = 1.,\n breaks = \"jenks\", # algorithm to categorise the data\n lwd = 0.1, \n border = 'grey') # boundary colour\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTask\nWhat is the key pattern emerging from this map?\n\n\n\nLet us now explore ggplot or tmap.\nUsing ggplot\nWe can visualise spatial data frames using ggplot fairly easily. ggplot is a generic sets of functions which was not specifically designed for spatial mapping, but it is fairly flexible that allows producing great spatial data visualisations.\nFollowing the grammar of ggplot, we plot spatial data drawing layers. ggplot has a basic structure of three components:\n\nThe data i.e. ggplot( data = *data frame*).\nGeometries i.e. geom_xxx( ).\nAesthetic mapping i.e. aes(x=*variable*, y=*variable*)\n\n\nWe can put these three components together using +. This is similar to the ways we apply the pipe operator in for tidyverse. The latter component of aesthetic mapping can be added in both the ggplot and geom_xxx functions.\nTo map our data, we can then run:\n\nggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map\n geom_sf(colour = \"gray60\", # colour line\n size = 0.1) # line size\n\n\n\n\n\n\n\nWe can change the colour palette by using a different colour palette. We can use the viridis package. The power of viridis is that it uses color scales that visually pleasing, colorblind friendly, print well in gray scale, and can be used for both categorical and continuous data. For categorical data you can also use ColourBrewer.\nSo let us (1) change the colour pallete to viridis, (2) remove the colour of the boundaries, and (3) replace the theme with the theme we will be using for the book. Let’s read the theme first and then implement these changes.\nThe ggplot themes for the book are in a file called data-visualisation_theme.R in the style folder. We read this file by running:\n\nsource(\"./style/data-visualisation_theme.R\")\n\nLoading required package: sysfonts\n\n\nLoading required package: showtextdb\n\n\nWe can now implement our changes:\n\nggplot(data = oa_shp, aes( fill = Ethnic) ) + # add data frame and variable to map\n geom_sf(colour = \"transparent\") + # colour line\n scale_fill_viridis( option = \"viridis\" ) + # add viridis colour scheme\n theme_map_tufte()\n\n\n\n\n\n\n\nTo master ggplot, see Wickham (2009).\nUsing tmap\nSimilar to ggplot2, tmap is based on the idea of a ‘grammar of graphics’ which involves a separation between the input data and aesthetics (i.e. the way data are visualised). Each data set can be mapped in various different ways, including location as defined by its geometry, colour and other features. The basic building block is tm_shape() (which defines input data), followed by one or more layer elements such as tm_fill() and tm_dots().\n\n# ensure geometry is valid\noa_shp = sf::st_make_valid(oa_shp)\n\n# map\nlegend_title = expression(\"% ethnic pop.\")\nmap_oa = tm_shape(oa_shp) +\n tm_fill(col = \"Ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .01) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) # add scale bar\nmap_oa\n\n\n\n\n\n\n\nNote that the operation +, as for ggplot is used to add new layers. You can set style themes by tm_style. To visualise the existing styles use tmap_style_catalogue(). An advantage of tmap is that you can easily create an interactive map by running tmap_mode.\n\ntmap_mode(\"view\")\n\ntmap mode set to interactive viewing\n\nmap_oa\n\nCompass not supported in view mode.\n\n\nWarning: In view mode, scale bar breaks are ignored.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTask\nTry mapping other variables in the spatial data frame. Where do population aged 60 and over concentrate?\n\n\n\n\n3.10.3 Comparing geographies\nIf you recall, one of the key issues of working with spatial data is the modifiable area unit problem (MAUP) - see (spatial_data?). To get a sense of the effects of MAUP, we analyse differences in the spatial patterns of the ethnic population in Liverpool between Middle Layer Super Output Areas (MSOAs) and OAs. So we map these geographies together.\n\n\n\n\n\n\n\n\nNote\n\n\n\nThe first line of the chunk code include tmap_mode(\"plot\") which tells R that we want a static map. tmap_mode works like a switch to interactive and non-interactive mapping.\n\n\n\ntmap_mode(\"plot\")\n\ntmap mode set to plotting\n\n# read data at the msoa level\nmsoa_shp <- st_read(\"data/census/Liverpool_MSOA.shp\")\n\nReading layer `Liverpool_MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/census/Liverpool_MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 16 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"Ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \n\n# arrange maps \ntmap_arrange(map_msoa, map_oa) \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nTask\nWhat differences do you see between OAs and MSOAs?\nCan you identify areas of spatial clustering? Where are they?\n\n\n\n\n\nAppelhans, Tim, Florian Detsch, Christoph Reudenbach, and Stefan Woellauer. 2022. Mapview: Interactive Viewing of Spatial Data in r. https://github.com/r-spatial/mapview.\n\n\nBaddeley, Adrian, Ege Rubak, and Rolf Turner. 2015. Spatial Point Patterns: Methodology and Applications with r. CRC press.\n\n\nBaddeley, Adrian, Rolf Turner, and Ege Rubak. 2022. Spatstat: Spatial Point Pattern Analysis, Model-Fitting, Simulation, Tests. http://spatstat.org/.\n\n\nBivand, Roger. 2022. Spdep: Spatial Dependence: Weighting Schemes, Statistics.\n\n\nBivand, Roger, and Gianfranco Piras. 2022. Spatialreg: Spatial Regression Analysis. https://CRAN.R-project.org/package=spatialreg.\n\n\nDunnington, Dewey, Edzer Pebesma, and Ege Rubak. 2023. S2: Spherical Geometry Operators Using the S2 Geometry Library. https://CRAN.R-project.org/package=s2.\n\n\nGrolemund, Garrett, and Hadley Wickham. 2019. R for Data Science. O’Reilly, US. https://r4ds.had.co.nz.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\n———. 2024. Geocomputation with r. Online. https://doi.org/10.1201/9780203730058.\n\n\nPebesma, Edzer. 2004. “Multivariable Geostatistics in S: The Gstat Package.” Computers & Geosciences 30 (7): 683–91. https://doi.org/10.1016/j.cageo.2004.03.012.\n\n\n———. 2018. “Simple Features for R: Standardized Support for Spatial Vector Data.” The R Journal 10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009.\n\n\n———. 2022a. Sf: Simple Features for r. https://CRAN.R-project.org/package=sf.\n\n\n———. 2022b. Stars: Spatiotemporal Arrays, Raster and Vector Data Cubes. https://CRAN.R-project.org/package=stars.\n\n\n———. 2023. Lwgeom: Bindings to Selected Liblwgeom Functions for Simple Features. https://github.com/r-spatial/lwgeom/.\n\n\nPebesma, Edzer, and Roger Bivand. 2023. Spatial Data Science: With Applications in r. CRC Press.\n\n\nPebesma, Edzer, and Benedikt Graeler. 2022. Gstat: Spatial and Spatio-Temporal Geostatistical Modelling, Prediction and Simulation. https://github.com/r-spatial/gstat/.\n\n\nTennekes, Martijn. 2018. “tmap: Thematic Maps in R.” Journal of Statistical Software 84 (6): 1–39. https://doi.org/10.18637/jss.v084.i06.\n\n\n———. 2022. Tmap: Thematic Maps. https://github.com/r-tmap/tmap.\n\n\nWickham, Hadley. 2009. Ggplot2. Springer New York. https://doi.org/10.1007/978-0-387-98141-3.\n\n\nWickham, Hadley, Mara Averick, Jennifer Bryan, Winston Chang, Lucy D’Agostino McGowan, Romain François, Garrett Grolemund, et al. 2019. “Welcome to the tidyverse.” Journal of Open Source Software 4 (43): 1686. https://doi.org/10.21105/joss.01686.\n\n\nWickham, Hadley, Mine Çetinkaya-Rundel, and Garrett Grolemund. 2023. R for Data Science. \" O’Reilly Media, Inc.\".\n\n\nWilliamson, Paul. 2018. “Survey Analysis.”\n\n\nXie, Yihui, JJ Allaire, and Garrett Grolemund. 2019. R Markdown: The Definitive Guide. CRC Press, Taylor & Francis, Chapman & Hall Book. https://bookdown.org/yihui/rmarkdown/.", + "crumbs": [ + "3  Data Wrangling" + ] }, { - "objectID": "03-data-wrangling.html#useful-functions", - "href": "03-data-wrangling.html#useful-functions", - "title": "3  Data Wrangling", - "section": "3.11 Useful Functions", - "text": "3.11 Useful Functions\n\n\n\n\n\n\n\nFunction\nDescription\n\n\n\n\nread.csv()\nread csv files into data frames\n\n\nstr()\ninspect data structure\n\n\nmutate()\ncreate a new variable\n\n\nfilter()\nfilter observations based on variable values\n\n\n%>%\npipe operator - chain operations\n\n\nselect()\nselect variables\n\n\nmerge()\njoin dat frames\n\n\nst_read\nread spatial data (ie. shapefiles)\n\n\nplot()\ncreate a map based a spatial data set\n\n\ntm_shape(), tm_fill(), tm_borders()\ncreate a map using tmap functions\n\n\ntm_arrange\ndisplay multiple maps in a single “metaplot”\n\n\n\n\n\n\n\nGrolemund, Garrett, and Hadley Wickham. 2019. R for Data Science. O’Reilly, US. https://r4ds.had.co.nz.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\nWilliamson, Paul. 2018. “Survey Analysis.”\n\n\nXie, Yihui, JJ Allaire, and Garrett Grolemund. 2019. R Markdown: The Definitive Guide. CRC Press, Taylor & Francis, Chapman & Hall Book. https://bookdown.org/yihui/rmarkdown/." - }, - { - "objectID": "04-points.html#dependencies", - "href": "04-points.html#dependencies", + "objectID": "04-points.html", + "href": "04-points.html", "title": "4  Point Data Analysis", - "section": "4.1 Dependencies", - "text": "4.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n\n# For pretty table\nlibrary(knitr)\n# All things geodata\nlibrary(sf)\nlibrary(sp)\n# Pretty graphics\nlibrary(ggplot2)\nlibrary(gridExtra)\n# Thematic maps\nlibrary(tmap)\n# Pretty maps\nlibrary(ggmap)\n# For all your interpolation needs\nlibrary(gstat)\n# For data manipulation\nlibrary(plyr)\n\nBefore we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the house_transactions folder with the data lives." + "section": "", + "text": "4.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n# data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# spatial data manipulation\nlibrary(sf)\nlibrary(sp)\n# data visualisation\nlibrary(gridExtra)\n# basemap\nlibrary(basemapR)\n# interpolation\nlibrary(gstat)\nlibrary(hexbin)\nBefore we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the house_transactions folder with the data lives.", + "crumbs": [ + "4  Point Data Analysis" + ] }, { "objectID": "04-points.html#data", "href": "04-points.html#data", "title": "4  Point Data Analysis", - "section": "4.2 Data", - "text": "4.2 Data\nFor this session, we will use the set of Airbnb properties for San Diego (US), borrowed from the “Geographic Data Science with Python” book (see here for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region.\nLet us start by reading the data, which comes in a GeoJSON:\n\ndb <- st_read(\"data/abb_sd/regression_db.geojson\")\n\nReading layer `regression_db' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/abb_sd/regression_db.geojson' \n using driver `GeoJSON'\nSimple feature collection with 6110 features and 19 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2812 ymin: 32.57349 xmax: -116.9553 ymax: 33.08311\nGeodetic CRS: WGS 84\n\n\nWe can then examine the columns of the table with the colnames method:\n\ncolnames(db)\n\n [1] \"accommodates\" \"bathrooms\" \"bedrooms\" \n [4] \"beds\" \"neighborhood\" \"pool\" \n [7] \"d2balboa\" \"coastal\" \"price\" \n[10] \"log_price\" \"id\" \"pg_Apartment\" \n[13] \"pg_Condominium\" \"pg_House\" \"pg_Other\" \n[16] \"pg_Townhouse\" \"rt_Entire_home.apt\" \"rt_Private_room\" \n[19] \"rt_Shared_room\" \"geometry\" \n\n\nThe rest of this session will focus on two main elements of the table: the spatial dimension (as stored in the point coordinates), and the nightly price values, expressed in USD and contained in the price column. To get a sense of what they look like first, let us plot both. We can get a quick look at the non-spatial distribution of house values with the following commands:\n\n# Create the histogram\nhist <- qplot(data=db,x=price)\n\nWarning: `qplot()` was deprecated in ggplot2 3.4.0.\n\nhist\n\n`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\n\n\n\n\n\nRaw AirBnb prices in San Diego\n\n\n\n\nThis basically shows there is a lot of values concentrated around the lower end of the distribution but a few very large ones. A usual transformation to shrink these differences is to take logarithms. The original table already contains an additional column with the logarithm of each price (log_price).\n\n# Create the histogram\nhist <- qplot(data=db, x=log_price)\nhist\n\n`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\n\n\n\n\n\nLog of AirBnb price in San Diego\n\n\n\n\nTo obtain the spatial distribution of these houses, we need to focus on the geometry column. The easiest, quickest (and also “dirtiest”) way to get a sense of what the data look like over space is using plot:\n\nplot(st_geometry(db))\n\n\n\n\nSpatial distribution of AirBnb in San Diego\n\n\n\n\nNow this has the classic problem of cluttering: some portions of the map have so many points that we can’t tell what the distribution is like. To get around this issue, there are two solutions: binning and smoothing." + "section": "\n4.2 Data", + "text": "4.2 Data\nFor this session, we will use the set of Airbnb properties for San Diego (US), borrowed from the “Geographic Data Science with Python” book (see here for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region.\nLet us start by reading the data, which comes in a GeoJSON:\n\ndb <- st_read(\"data/abb_sd/regression_db.geojson\")\n\nReading layer `regression_db' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/abb_sd/regression_db.geojson' \n using driver `GeoJSON'\nSimple feature collection with 6110 features and 19 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2812 ymin: 32.57349 xmax: -116.9553 ymax: 33.08311\nGeodetic CRS: WGS 84\n\n\nWe can then examine the columns of the table with the colnames method:\n\ncolnames(db)\n\n [1] \"accommodates\" \"bathrooms\" \"bedrooms\" \n [4] \"beds\" \"neighborhood\" \"pool\" \n [7] \"d2balboa\" \"coastal\" \"price\" \n[10] \"log_price\" \"id\" \"pg_Apartment\" \n[13] \"pg_Condominium\" \"pg_House\" \"pg_Other\" \n[16] \"pg_Townhouse\" \"rt_Entire_home.apt\" \"rt_Private_room\" \n[19] \"rt_Shared_room\" \"geometry\" \n\n\nThe rest of this session will focus on two main elements of the table: the spatial dimension (as stored in the point coordinates), and the nightly price values, expressed in USD and contained in the price column. To get a sense of what they look like first, let us plot both. We can get a quick look at the non-spatial distribution of house values with the following commands:\n\n# Create the histogram\nqplot( data = db, x = price)\n\nWarning: `qplot()` was deprecated in ggplot2 3.4.0.\n\n\n`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\n\n\n\n\nRaw AirBnb prices in San Diego\n\n\n\nThis basically shows there is a lot of values concentrated around the lower end of the distribution but a few very large ones. A usual transformation to shrink these differences is to take logarithms. The original table already contains an additional column with the logarithm of each price (log_price).\n\n# Create the histogram\nqplot( data = db, x = log_price )\n\n`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.\n\n\n\n\n\n\n\n\nTo obtain the spatial distribution of these houses, we need to focus on the geometry column. The easiest, quickest (and also “dirtiest”) way to get a sense of what the data look like over space is using plot:\n\nplot(st_geometry(db))\n\n\n\n\n\n\n\nNow this has the classic problem of cluttering: some portions of the map have so many points that we can’t tell what the distribution is like. To get around this issue, there are two solutions: binning and smoothing.", + "crumbs": [ + "4  Point Data Analysis" + ] }, { "objectID": "04-points.html#binning", "href": "04-points.html#binning", "title": "4  Point Data Analysis", - "section": "4.3 Binning", - "text": "4.3 Binning\nThe two-dimensional sister of histograms are binning maps: we divide each of the two dimensions into “buckets”, and count how many points fall within each bucket. Unlike histograms, we encode that count with a color gradient rather than a bar chart over an additional dimension (for that, we would need a 3D plot). These “buckets” can be squares (left) or hexagons (right):\n\n # Squared binning\n# Set up plot\nsqbin <- ggplot() + \n# Add 2D binning with the XY coordinates as\n# a dataframe\n geom_bin2d(\n data=as.data.frame(st_coordinates(db)), \n aes(x=X, y=Y)\n )\n # Hex binning\n# Set up plot\nhexbin <- ggplot() +\n# Add hex binning with the XY coordinates as\n# a dataframe \n geom_hex(\n data=as.data.frame(st_coordinates(db)),\n aes(x=X, y=Y)\n ) +\n# Use viridis for color encoding (recommended)\n scale_fill_continuous(type = \"viridis\")\n # Bind in subplots\ngrid.arrange(sqbin, hexbin, ncol=2)" + "section": "\n4.3 Binning", + "text": "4.3 Binning\nThe two-dimensional sister of histograms are binning maps: we divide each of the two dimensions into “buckets”, and count how many points fall within each bucket. Unlike histograms, we encode that count with a color gradient rather than a bar chart over an additional dimension (for that, we would need a 3D plot). These “buckets” can be squares (left) or hexagons (right):\n\n # Squared binning\n# Set up plot\nsqbin <- ggplot( ) + \n# Add 2D binning with the XY coordinates as\n# a dataframe\n geom_bin2d(\n data = as.data.frame( st_coordinates( db ) ), \n aes( x = X, y = Y)\n ) + \n # set theme \n theme_plot_tufte()\n # Hex binning\n# Set up plot\nhexbin <- ggplot() +\n# Add hex binning with the XY coordinates as\n# a dataframe \n geom_hex(\n data = as.data.frame( st_coordinates( db ) ),\n aes( x = X, y = Y)\n ) +\n# Use viridis for color encoding (recommended)\n scale_fill_continuous( type = \"viridis\" ) +\n theme_plot_tufte()\n # Bind in subplots\ngrid.arrange( sqbin, hexbin, ncol = 2 )", + "crumbs": [ + "4  Point Data Analysis" + ] }, { "objectID": "04-points.html#kde", "href": "04-points.html#kde", "title": "4  Point Data Analysis", - "section": "4.4 KDE", - "text": "4.4 KDE\nKernel Density Estimation (KDE) is a technique that creates a continuous representation of the distribution of a given variable, such as house prices. Although theoretically it can be applied to any dimension, usually, KDE is applied to either one or two dimensions.\n\n4.4.1 One-dimensional KDE\nKDE over a single dimension is essentially a contiguous version of a histogram. We can see that by overlaying a KDE on top of the histogram of logs that we have created before:\n\n# Create the base\nbase <- ggplot(db, aes(x=log_price))\n# Histogram\nhist <- base + \n geom_histogram(bins=50, aes(y=..density..))\n# Overlay density plot\nkde <- hist + \n geom_density(fill=\"#FF6666\", alpha=0.5, colour=\"#FF6666\")\nkde\n\nWarning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.\nℹ Please use `after_stat(density)` instead.\n\n\n\n\n\nHistogram and KDE of the log of AirBnb prices in San Diego\n\n\n\n\nThe key idea is that we are smoothing out the discrete binning that the histogram involves. Note how the histogram is exactly the same as above shape-wise, but it has been rescalend on the Y axis to reflect probabilities rather than counts.\n\n\n4.4.2 Two-dimensional (spatial) KDE\nGeography, at the end of the day, is usually represented as a two-dimensional space where we locate objects using a system of dual coordinates, X and Y (or latitude and longitude). Thanks to that, we can use the same technique as above to obtain a smooth representation of the distribution of a two-dimensional variable. The crucial difference is that, instead of obtaining a curve as the output, we will create a surface, where intensity will be represented with a color gradient, rather than with the second dimension, as it is the case in the figure above.\nTo create a spatial KDE in R, we can use general tooling for non-spatial points, such as the stat_density2d_filled method:\n\n# Create the KDE surface\nkde <- ggplot(data = db) +\n stat_density2d_filled(alpha = 0.5,\n data = as.data.frame(st_coordinates(db)), \n aes(x = X, y = Y),\n n = 16\n ) +\n # Tweak the color gradient\n scale_color_viridis_c() +\n # White theme\n theme_bw()\n# Tip! Add invisible points to improve proportions\nkde + geom_sf(alpha=0)\n\n\n\n\nKDE of AirBnb properties in San Diego\n\n\n\n\nThis approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library ggmap, which is designed to play nicely with the ggplot2 family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though.\n\n# Reproject coordinates\nlon_lat <- st_transform(db, crs = 4326) %>%\n st_coordinates() %>%\n as.data.frame()\n# Basemap\nqmplot(\n X, \n Y, \n data = lon_lat, \n geom=\"blank\"\n) +\n # KDE\n stat_density2d_filled(alpha = 0.5,\n data = lon_lat, \n aes(x = X, y = Y),\n n = 16\n ) +\n # Tweak the color gradient\n scale_color_viridis_c()\n\n\n\n\nKDE of AirBnb properties in San Diego" + "section": "\n4.4 KDE", + "text": "4.4 KDE\nKernel Density Estimation (KDE) is a technique that creates a continuous representation of the distribution of a given variable, such as house prices. Although theoretically it can be applied to any dimension, usually, KDE is applied to either one or two dimensions.\n\n4.4.1 One-dimensional KDE\nKDE over a single dimension is essentially a contiguous version of a histogram. We can see that by overlaying a KDE on top of the histogram of logs that we have created before:\n\n# Create the base\nbase <- ggplot(db, aes(x=log_price))\n# Histogram\nhist <- base + \n geom_histogram(bins=50, aes(y=..density..))\n# Overlay density plot\nkde <- hist + \n geom_density(fill=\"#FF6666\", alpha=0.5, colour=\"#FF6666\") +\n theme_plot_tufte()\nkde\n\nWarning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.\nℹ Please use `after_stat(density)` instead.\n\n\n\n\n\n\n\n\nThe key idea is that we are smoothing out the discrete binning that the histogram involves. Note how the histogram is exactly the same as above shape-wise, but it has been rescalend on the Y axis to reflect probabilities rather than counts.\n\n4.4.2 Two-dimensional (spatial) KDE\nGeography, at the end of the day, is usually represented as a two-dimensional space where we locate objects using a system of dual coordinates, X and Y (or latitude and longitude). Thanks to that, we can use the same technique as above to obtain a smooth representation of the distribution of a two-dimensional variable. The crucial difference is that, instead of obtaining a curve as the output, we will create a surface, where intensity will be represented with a color gradient, rather than with the second dimension, as it is the case in the figure above.\nTo create a spatial KDE in R, we can use general tooling for non-spatial points, such as the stat_density2d_filled method:\n\n# Create the KDE surface\nkde <- ggplot(data = db) +\n stat_density2d_filled(alpha = 1,\n data = as.data.frame(st_coordinates(db)), \n aes(x = X, y = Y),\n n = 100\n ) +\n # Tweak the color gradient\n scale_color_viridis_c() +\n # White theme\n theme_plot_tufte() \nkde\n\n\n\n\n\n\n\nThis approach generates a surface that represents the density of dots, that is an estimation of the probability of finding a house transaction at a given coordinate. However, without any further information, they are hard to interpret and link with previous knowledge of the area. To bring such context to the figure, we can plot an underlying basemap, using a cloud provider such as Google Maps or, as in this case, OpenStreetMap. To do it, we will leverage the library basemapR, which is designed to play nicely with the ggplot2 family (hence the seemingly counterintuitive example above). Before we can plot them with the online map, we need to reproject them though.\n\nbbox_db <- st_bbox(db)\nggplot() +\n base_map(bbox_db, increase_zoom = 2, basemap = \"positron\") +\n #geom_sf(data = db, fill = NA) +\n stat_density2d_filled(alpha = 0.7,\n data = as.data.frame(st_coordinates(db)), \n aes(x = X, y = Y),\n n = 100\n )", + "crumbs": [ + "4  Point Data Analysis" + ] }, { "objectID": "04-points.html#spatial-interpolation", "href": "04-points.html#spatial-interpolation", "title": "4  Point Data Analysis", - "section": "4.5 Spatial Interpolation", - "text": "4.5 Spatial Interpolation\nThe previous section demonstrates how to visualize the distribution of a set of spatial objects represented as points. In particular, given a bunch of house locations, it shows how one can effectively visualize their distribution over space and get a sense of the density of occurrences. Such visualization, because it is based on KDE, is based on a smooth continuum, rather than on a discrete approach (as a choropleth would do, for example).\nMany times however, we are not particularly interested in learning about the density of occurrences, but about the distribution of a given value attached to each location. Think for example of weather stations and temperature: the location of the stations is no secret and rarely changes, so it is not of particular interest to visualize the density of stations; what we are usually interested instead is to know how temperature is distributed over space, given we only measure it in a few places. One could argue the example we have been working with so far, house prices in AirBnb, fits into this category as well: although where a house is advertised may be of relevance, more often we are interested in finding out what the “surface of price” looks like. Rather than where are most houses being advertised? we usually want to know where the most expensive or most affordable houses are located.\nIn cases where we are interested in creating a surface of a given value, rather than a simple density surface of occurrences, KDE cannot help us. In these cases, what we are interested in is spatial interpolation, a family of techniques that aim at exactly that: creating continuous surfaces for a particular phenomenon (e.g. temperature, house prices) given only a finite sample of observations. Spatial interpolation is a large field of research that is still being actively developed and that can involve a substantial amount of mathematical complexity in order to obtain the most accurate estimates possible1. In this chapter, we will introduce the simplest possible way of interpolating values, hoping this will give you a general understanding of the methodology and, if you are interested, you can check out further literature. For example, Banerjee, Carlin, and Gelfand (2014) or Cressie (2015) are hard but good overviews.\n\n4.5.1 Inverse Distance Weight (IDW) interpolation\nThe technique we will cover here is called Inverse Distance Weighting, or IDW for convenience. Brunsdon and Comber (2015) offer a good description:\n\nIn the inverse distance weighting (IDW) approach to interpolation, to estimate the value of \\(z\\) at location \\(x\\) a weighted mean of nearby observations is taken […]. To accommodate the idea that observations of \\(z\\) at points closer to \\(x\\) should be given more importance in the interpolation, greater weight is given to these points […]\n— Page 204\n\nThe math2 is not particularly complicated and may be found in detail elsewhere (the reference above is a good starting point), so we will not spend too much time on it. More relevant in this context is the intuition behind. The idea is that we will create a surface of house price by smoothing many values arranged along a regular grid and obtained by interpolating from the known locations to the regular grid locations. This will give us full and equal coverage to soundly perform the smoothing.\nEnough chat, let’s code3.\nFrom what we have just mentioned, there are a few steps to perform an IDW spatial interpolation:\n\nCreate a regular grid over the area where we have house transactions.\nObtain IDW estimates for each point in the grid, based on the values of \\(k\\) nearest neighbors.\nPlot a smoothed version of the grid, effectively representing the surface of house prices.\n\nLet us go in detail into each of them4. First, let us set up a grid for the extent of the bounding box of our data (not the use of pipe, %>%, operator to chain functions):\n\nsd.grid <- db %>%\n st_bbox() %>%\n st_as_sfc() %>%\n st_make_grid(\n n = 100,\n what = \"centers\"\n ) %>%\n st_as_sf() %>%\n cbind(., st_coordinates(.))\n\nThe object sd.grid is a regular grid with 10,000 (\\(100 \\times 100\\)) equally spaced cells:\n\nsd.grid\n\nSimple feature collection with 10000 features and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2795 ymin: 32.57604 xmax: -116.9569 ymax: 33.08056\nGeodetic CRS: WGS 84\nFirst 10 features:\n X Y x\n1 -117.2795 32.57604 POINT (-117.2795 32.57604)\n2 -117.2763 32.57604 POINT (-117.2763 32.57604)\n3 -117.2730 32.57604 POINT (-117.273 32.57604)\n4 -117.2698 32.57604 POINT (-117.2698 32.57604)\n5 -117.2665 32.57604 POINT (-117.2665 32.57604)\n6 -117.2632 32.57604 POINT (-117.2632 32.57604)\n7 -117.2600 32.57604 POINT (-117.26 32.57604)\n8 -117.2567 32.57604 POINT (-117.2567 32.57604)\n9 -117.2535 32.57604 POINT (-117.2535 32.57604)\n10 -117.2502 32.57604 POINT (-117.2502 32.57604)\n\n\nNow, sd.grid only contain the location of points to which we wish to interpolate. That is, we now have our “target” geography for which we’d like to have AirBnb prices, but we don’t have price estimates. For that, on to the IDW, which will generate estimates for locations in sd.grid based on the observed prices in db. Again, this is hugely simplified by gstat:\n\nidw.hp <- idw(\n price ~ 1, # Formula for IDW\n locations = db, # Initial locations with values\n newdata=sd.grid, # Locations we want predictions for\n nmax = 150 # Limit the number of neighbours for IDW\n)\n\n[inverse distance weighted interpolation]\n\n\nBoom! We’ve got it. Let us pause for a second to see how we just did it. First, we pass price ~ 1. This specifies the formula we are using to model house prices. The name on the left of ~ represents the variable we want to explain, while everything to its right captures the explanatory variables. Since we are considering the simplest possible case, we do not have further variables to add, so we simply write 1. Then we specify the original locations for which we do have house prices (our original db object), and the points where we want to interpolate the house prices (the sd.grid object we just created above). One more note: by default, idw uses all the available observations, weighted by distance, to provide an estimate for a given point. If you want to modify that and restrict the maximum number of neighbors to consider, you need to tweak the argument nmax, as we do above by using the 150 nearest observations to each point5.\nThe object we get from idw is another spatial table, just as db, containing the interpolated values. As such, we can inspect it just as with any other of its kind. For example, to check out the top of the estimated table:\n\nhead(idw.hp)\n\nSimple feature collection with 6 features and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2795 ymin: 32.57604 xmax: -117.2632 ymax: 32.57604\nGeodetic CRS: WGS 84\n var1.pred var1.var geometry\n1 295.6100 NA POINT (-117.2795 32.57604)\n2 295.1651 NA POINT (-117.2763 32.57604)\n3 296.5927 NA POINT (-117.273 32.57604)\n4 288.2252 NA POINT (-117.2698 32.57604)\n5 281.5522 NA POINT (-117.2665 32.57604)\n6 268.3567 NA POINT (-117.2632 32.57604)\n\n\nThe column we will pay attention to is var1.pred. For a hypothetical house advertised at the location in the first row of point in sd.grid, the price IDW would guess it would cost, based on prices nearby, is the first element of column var1.pred in idw.hp.\n\n\n4.5.2 A surface of housing prices\nOnce we have the IDW object computed, we can plot it to explore the distribution, not of AirBnb locations in this case, but of house prices over the geography of San Diego. To do this using ggplot2, we first append the coordinates of each grid cell as columns of the table:\n\nidw.hp = idw.hp %>%\n cbind(st_coordinates(.))\n\nNow, we can visualise the surface using standard ggplot2 tools:\n\nggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +\n geom_raster()\n\n\n\n\nAnd we can “dress it up” a bit further:\n\nggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +\n geom_raster() +\n scale_fill_viridis_b() +\n theme_void() +\n geom_sf(alpha=0)\n\n\n\n\nLooking at this, we can start to tell some patterns. To bring in context, it would be great to be able to add a basemap layer, as we did for the KDE. This is conceptually very similar to what we did above, starting by reprojecting the points and continuing by overlaying them on top of the basemap. However, technically speaking it is not possible because ggmap –the library we have been using to display tiles from cloud providers– does not play well with our own rasters (i.e. the price surface). At the moment, it is surprisingly tricky to get this to work, so we will park it for now6.\n\n\n4.5.3 “What should the next house’s price be?”\nThe last bit we will explore in this session relates to prediction for new values. Imagine you are a real state data scientist working for Airbnb and your boss asks you to give an estimate of how much a new house going into the market should cost. The only information you have to make such a guess is the location of the house. In this case, the IDW model we have just fitted can help you. The trick is realizing that, instead of creating an entire grid, all we need is to obtain an estimate of a single location.\nLet us say, a new house is going to be advertised on the coordinates X = -117.02259063720702, Y = 32.76511965117273 as expressed in longitude and latitude. In that case, we can do as follows:\n\npt <- c(X = -117.02259063720702, Y = 32.76511965117273) %>%\n st_point() %>%\n st_sfc() %>%\n st_sf(crs = \"EPSG:4326\") %>%\n st_transform(st_crs(db))\nidw.one <- idw(price ~ 1, locations=db, newdata=pt)\n\n[inverse distance weighted interpolation]\n\nidw.one\n\nSimple feature collection with 1 feature and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.0226 ymin: 32.76512 xmax: -117.0226 ymax: 32.76512\nGeodetic CRS: WGS 84\n var1.pred var1.var geometry\n1 171.4141 NA POINT (-117.0226 32.76512)\n\n\nAnd, as show above, the estimated value is $171.41413347." + "section": "\n4.5 Spatial Interpolation", + "text": "4.5 Spatial Interpolation\nThe previous section demonstrates how to visualize the distribution of a set of spatial objects represented as points. In particular, given a bunch of house locations, it shows how one can effectively visualize their distribution over space and get a sense of the density of occurrences. Such visualization, because it is based on KDE, is based on a smooth continuum, rather than on a discrete approach (as a choropleth would do, for example).\nMany times however, we are not particularly interested in learning about the density of occurrences, but about the distribution of a given value attached to each location. Think for example of weather stations and temperature: the location of the stations is no secret and rarely changes, so it is not of particular interest to visualize the density of stations; what we are usually interested instead is to know how temperature is distributed over space, given we only measure it in a few places. One could argue the example we have been working with so far, house prices in AirBnb, fits into this category as well: although where a house is advertised may be of relevance, more often we are interested in finding out what the “surface of price” looks like. Rather than where are most houses being advertised? we usually want to know where the most expensive or most affordable houses are located.\nIn cases where we are interested in creating a surface of a given value, rather than a simple density surface of occurrences, KDE cannot help us. In these cases, what we are interested in is spatial interpolation, a family of techniques that aim at exactly that: creating continuous surfaces for a particular phenomenon (e.g. temperature, house prices) given only a finite sample of observations. Spatial interpolation is a large field of research that is still being actively developed and that can involve a substantial amount of mathematical complexity in order to obtain the most accurate estimates possible1. In this chapter, we will introduce the simplest possible way of interpolating values, hoping this will give you a general understanding of the methodology and, if you are interested, you can check out further literature. For example, Banerjee, Carlin, and Gelfand (2014) or Cressie (2015) are hard but good overviews.\n1 There is also an important economic incentive to do this: some of the most popular applications are in the oil and gas or mining industries. In fact, the very creator of this technique, Danie G. Krige, was a mining engineer. His name is usually used to nickname spatial interpolation as kriging.\n4.5.1 Inverse Distance Weight (IDW) interpolation\nThe technique we will cover here is called Inverse Distance Weighting, or IDW for convenience. Brunsdon and Comber (2015) offer a good description:\n\nIn the inverse distance weighting (IDW) approach to interpolation, to estimate the value of \\(z\\) at location \\(x\\) a weighted mean of nearby observations is taken […]. To accommodate the idea that observations of \\(z\\) at points closer to \\(x\\) should be given more importance in the interpolation, greater weight is given to these points […]\n— Page 204\n\nThe math2 is not particularly complicated and may be found in detail elsewhere (the reference above is a good starting point), so we will not spend too much time on it. More relevant in this context is the intuition behind. The idea is that we will create a surface of house price by smoothing many values arranged along a regular grid and obtained by interpolating from the known locations to the regular grid locations. This will give us full and equal coverage to soundly perform the smoothing.\n2 Essentially, for any point \\(x\\) in space, the IDW estimate for value \\(z\\) is equivalent to \\(\\hat{z} (x) = \\dfrac{\\sum_i w_i z_i}{\\sum_i w_i}\\) where \\(i\\) are the observations for which we do have a value, and \\(w_i\\) is a weight given to location \\(i\\) based on its distance to \\(x\\).3 If you want a complementary view of point interpolation in R, you can read more on this fantastic blog postEnough chat, let’s code3.\nFrom what we have just mentioned, there are a few steps to perform an IDW spatial interpolation:\n\nCreate a regular grid over the area where we have house transactions.\nObtain IDW estimates for each point in the grid, based on the values of \\(k\\) nearest neighbors.\nPlot a smoothed version of the grid, effectively representing the surface of house prices.\n\nLet us go in detail into each of them4. First, let us set up a grid for the extent of the bounding box of our data (not the use of pipe, %>%, operator to chain functions):\n4 For the relevant calculations, we will be using the gstat library.\nsd.grid <- db %>%\n st_bbox() %>%\n st_as_sfc() %>%\n st_make_grid(\n n = 100,\n what = \"centers\"\n ) %>%\n st_as_sf() %>%\n cbind(., st_coordinates(.))\n\nThe object sd.grid is a regular grid with 10,000 (\\(100 \\times 100\\)) equally spaced cells:\n\nsd.grid\n\nSimple feature collection with 10000 features and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2795 ymin: 32.57604 xmax: -116.9569 ymax: 33.08056\nGeodetic CRS: WGS 84\nFirst 10 features:\n X Y x\n1 -117.2795 32.57604 POINT (-117.2795 32.57604)\n2 -117.2763 32.57604 POINT (-117.2763 32.57604)\n3 -117.2730 32.57604 POINT (-117.273 32.57604)\n4 -117.2698 32.57604 POINT (-117.2698 32.57604)\n5 -117.2665 32.57604 POINT (-117.2665 32.57604)\n6 -117.2632 32.57604 POINT (-117.2632 32.57604)\n7 -117.2600 32.57604 POINT (-117.26 32.57604)\n8 -117.2567 32.57604 POINT (-117.2567 32.57604)\n9 -117.2535 32.57604 POINT (-117.2535 32.57604)\n10 -117.2502 32.57604 POINT (-117.2502 32.57604)\n\n\nNow, sd.grid only contain the location of points to which we wish to interpolate. That is, we now have our “target” geography for which we’d like to have AirBnb prices, but we don’t have price estimates. For that, on to the IDW, which will generate estimates for locations in sd.grid based on the observed prices in db. Again, this is hugely simplified by gstat:\n\nidw.hp <- idw(\n price ~ 1, # Formula for IDW\n locations = db, # Initial locations with values\n newdata=sd.grid, # Locations we want predictions for\n nmax = 150 # Limit the number of neighbours for IDW\n)\n\n[inverse distance weighted interpolation]\n\n\nBoom! We’ve got it. Let us pause for a second to see how we just did it. First, we pass price ~ 1. This specifies the formula we are using to model house prices. The name on the left of ~ represents the variable we want to explain, while everything to its right captures the explanatory variables. Since we are considering the simplest possible case, we do not have further variables to add, so we simply write 1. Then we specify the original locations for which we do have house prices (our original db object), and the points where we want to interpolate the house prices (the sd.grid object we just created above). One more note: by default, idw uses all the available observations, weighted by distance, to provide an estimate for a given point. If you want to modify that and restrict the maximum number of neighbors to consider, you need to tweak the argument nmax, as we do above by using the 150 nearest observations to each point5.\n5 Have a play with this because the results do change significantly. Can you reason why?The object we get from idw is another spatial table, just as db, containing the interpolated values. As such, we can inspect it just as with any other of its kind. For example, to check out the top of the estimated table:\n\nhead(idw.hp)\n\nSimple feature collection with 6 features and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2795 ymin: 32.57604 xmax: -117.2632 ymax: 32.57604\nGeodetic CRS: WGS 84\n var1.pred var1.var geometry\n1 295.6100 NA POINT (-117.2795 32.57604)\n2 295.1651 NA POINT (-117.2763 32.57604)\n3 296.5927 NA POINT (-117.273 32.57604)\n4 288.2252 NA POINT (-117.2698 32.57604)\n5 281.5522 NA POINT (-117.2665 32.57604)\n6 268.3567 NA POINT (-117.2632 32.57604)\n\n\nThe column we will pay attention to is var1.pred. For a hypothetical house advertised at the location in the first row of point in sd.grid, the price IDW would guess it would cost, based on prices nearby, is the first element of column var1.pred in idw.hp.\n\n4.5.2 A surface of housing prices\nOnce we have the IDW object computed, we can plot it to explore the distribution, not of AirBnb locations in this case, but of house prices over the geography of San Diego. To do this using ggplot2, we first append the coordinates of each grid cell as columns of the table:\n\nidw.hp = idw.hp %>%\n cbind(st_coordinates(.))\n\nNow, we can visualise the surface using standard ggplot2 tools:\n\nggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +\n geom_raster()\n\n\n\n\n\n\n\nAnd we can “dress it up” a bit further:\n\nggplot(idw.hp, aes(x = X, y = Y, fill = var1.pred)) +\n geom_raster() +\n scale_fill_viridis_b() +\n theme_void() +\n geom_sf(alpha=0)\n\n\n\n\n\n\n\nLooking at this, we can start to tell some patterns. To bring in context, it would be great to be able to add a basemap layer, as we did for the KDE. This is conceptually very similar to what we did above, starting by reprojecting the points and continuing by overlaying them on top of the basemap. However, technically speaking it is not possible because ggmap –the library we have been using to display tiles from cloud providers– does not play well with our own rasters (i.e. the price surface). At the moment, it is surprisingly tricky to get this to work, so we will park it for now6.\n6 BONUS if you can figure out a way to do it yourself!\n4.5.3 “What should the next house’s price be?”\n\nThe last bit we will explore in this session relates to prediction for new values. Imagine you are a real state data scientist working for Airbnb and your boss asks you to give an estimate of how much a new house going into the market should cost. The only information you have to make such a guess is the location of the house. In this case, the IDW model we have just fitted can help you. The trick is realizing that, instead of creating an entire grid, all we need is to obtain an estimate of a single location.\nLet us say, a new house is going to be advertised on the coordinates X = -117.02259063720702, Y = 32.76511965117273 as expressed in longitude and latitude. In that case, we can do as follows:\n\npt <- c(X = -117.02259063720702, Y = 32.76511965117273) %>%\n st_point() %>%\n st_sfc() %>%\n st_sf(crs = \"EPSG:4326\") %>%\n st_transform(st_crs(db))\nidw.one <- idw(price ~ 1, locations=db, newdata=pt)\n\n[inverse distance weighted interpolation]\n\nidw.one\n\nSimple feature collection with 1 feature and 2 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.0226 ymin: 32.76512 xmax: -117.0226 ymax: 32.76512\nGeodetic CRS: WGS 84\n var1.pred var1.var geometry\n1 171.4141 NA POINT (-117.0226 32.76512)\n\n\nAnd, as show above, the estimated value is $171.41413347.\n7 PRO QUESTION Is that house expensive or cheap, as compared to the other houses sold in this dataset? Can you figure out where the house is in the distribution?", + "crumbs": [ + "4  Point Data Analysis" + ] }, { "objectID": "04-points.html#questions", "href": "04-points.html#questions", "title": "4  Point Data Analysis", - "section": "4.6 Questions", - "text": "4.6 Questions\nWe will be using the Madrid AirBnb dataset:\n\nmad_abb <- st_read(\"data/assignment_1_madrid/madrid_abb.gpkg\")\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\nThis is fairly similar in spirit to the one from San Diego we have relied on for the chapter, although the column set is not exactly the same:\n\ncolnames(mad_abb)\n\n [1] \"price\" \"price_usd\" \"log1p_price_usd\" \"accommodates\" \n [5] \"bathrooms_text\" \"bathrooms\" \"bedrooms\" \"beds\" \n [9] \"neighbourhood\" \"room_type\" \"property_type\" \"WiFi\" \n[13] \"Coffee\" \"Gym\" \"Parking\" \"km_to_retiro\" \n[17] \"geom\" \n\n\nFor this set of questions, the only two columns we will need is geom, which contains the point geometries, and price_usd, which record the price of the AirBnb property in USD.\nWith this at hand, answer the following questions:\n\nCreate a KDE that represents the density of locations of AirBnb properties in Madrid\nUsing inverse distance weighting, create a surface of AirBnb prices\n\n\n\n\n\nBanerjee, Sudipto, Bradley P Carlin, and Alan E Gelfand. 2014. Hierarchical Modeling and Analysis for Spatial Data. Crc Press.\n\n\nBivand, Roger S., Edzer Pebesma, and Virgilio Gómez-Rubio. 2013. Applied Spatial Data Analysis with r. Springer New York. https://doi.org/10.1007/978-1-4614-7618-4.\n\n\nBrunsdon, Chris, and Lex Comber. 2015. An Introduction to r for Spatial Analysis & Mapping. Sage.\n\n\nCressie, Noel. 2015. Statistics for Spatial Data. John Wiley & Sons.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058." + "section": "\n4.6 Questions", + "text": "4.6 Questions\nWe will be using the Madrid AirBnb dataset:\n\nmad_abb <- st_read(\"data/assignment_1_madrid/madrid_abb.gpkg\")\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\nThis is fairly similar in spirit to the one from San Diego we have relied on for the chapter, although the column set is not exactly the same:\n\ncolnames(mad_abb)\n\n [1] \"price\" \"price_usd\" \"log1p_price_usd\" \"accommodates\" \n [5] \"bathrooms_text\" \"bathrooms\" \"bedrooms\" \"beds\" \n [9] \"neighbourhood\" \"room_type\" \"property_type\" \"WiFi\" \n[13] \"Coffee\" \"Gym\" \"Parking\" \"km_to_retiro\" \n[17] \"geom\" \n\n\nFor this set of questions, the only two columns we will need is geom, which contains the point geometries, and price_usd, which record the price of the AirBnb property in USD.\nWith this at hand, answer the following questions:\n\nCreate a KDE that represents the density of locations of AirBnb properties in Madrid\nUsing inverse distance weighting, create a surface of AirBnb prices\n\n\n\n\n\n\n\nBanerjee, Sudipto, Bradley P Carlin, and Alan E Gelfand. 2014. Hierarchical Modeling and Analysis for Spatial Data. Crc Press.\n\n\nBivand, Roger S., Edzer Pebesma, and Virgilio Gómez-Rubio. 2013. Applied Spatial Data Analysis with r. Springer New York. https://doi.org/10.1007/978-1-4614-7618-4.\n\n\nBrunsdon, Chris, and Lex Comber. 2015. An Introduction to r for Spatial Analysis & Mapping. Sage.\n\n\nCressie, Noel. 2015. Statistics for Spatial Data. John Wiley & Sons.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.", + "crumbs": [ + "4  Point Data Analysis" + ] }, { - "objectID": "05-flows.html#dependencies", - "href": "05-flows.html#dependencies", + "objectID": "05-flows.html", + "href": "05-flows.html", "title": "5  Spatial Interaction Modelling", - "section": "5.1 Dependencies", - "text": "5.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n\n# Data management\nlibrary(tidyverse)\n# Spatial Data management\nlibrary(sf)\nlibrary(sp)\n# Pretty graphics\nlibrary(ggplot2)\n# Thematic maps\nlibrary(tmap)\n# Pretty maps\nlibrary(ggmap)\n# Simulation methods\nlibrary(arm)\n\nIn this chapter we will show a slightly different way of managing spatial data in R. Although most of the functionality will be similar to that seen in previous chapters, we will not rely on the “sf stack” and we will instead show how to read and manipulate data using the more traditional sp stack. Although this approach is being slowly phased out, it is still important to be aware of its existence and its differences with more modern approaches.\nBefore we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file -and where the sf_bikes folder with the data lives.\n\nsetwd('.')" + "section": "", + "text": "5.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n# Data management\nlibrary(tidyverse)\n# Spatial Data management\nlibrary(sf)\nlibrary(sp)\n# Pretty graphics\nlibrary(ggplot2)\n# Thematic maps\nlibrary(tmap)\n# Add basemaps\nlibrary(basemapR)\n# Simulation methods\nlibrary(arm)\nIn this chapter we will show a slightly different way of managing spatial data in R. Although most of the functionality will be similar to that seen in previous chapters, we will not rely on the “sf stack” and we will instead show how to read and manipulate data using the more traditional sp stack. Although this approach is being slowly phased out, it is still important to be aware of its existence and its differences with more modern approaches.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { "objectID": "05-flows.html#data", "href": "05-flows.html#data", "title": "5  Spatial Interaction Modelling", - "section": "5.2 Data", - "text": "5.2 Data\nIn this note, we will use data from the city of San Francisco representing bike trips on their public bike share system. The original source is the SF Open Data portal (link) and the dataset comprises both the location of each station in the Bay Area as well as information on trips (station of origin to station of destination) undertaken in the system from September 2014 to August 2015 and the following year. Since this note is about modeling and not data preparation, a cleanly reshaped version of the data, together with some additional information, has been created and placed in the sf_bikes folder. The data file is named flows.geojson and, in case you are interested, the (Python) code required to created from the original files in the SF Data Portal is also available on the flows_prep.ipynb notebook [url], also in the same folder.\nLet us then directly load the file with all the information necessary:\n\ndb <- st_read('./data/sf_bikes/flows.geojson')\n\nReading layer `flows' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/sf_bikes/flows.geojson' \n using driver `GeoJSON'\nSimple feature collection with 1722 features and 9 fields\nGeometry type: LINESTRING\nDimension: XY\nBounding box: xmin: -122.4237 ymin: 37.76914 xmax: -122.3875 ymax: 37.80737\nGeodetic CRS: WGS 84\n\n#rownames(db@data) <- db$flow_id\n#db@data$flow_id <- NULL\n\nNote how the interface is slightly different since we are reading a GeoJSON file instead of a shapefile.\nThe data contains the geometries of the flows, as calculated from the Google Maps API, as well as a series of columns with characteristics of each flow:\n\nhead(db)\n\nSimple feature collection with 6 features and 9 fields\nGeometry type: LINESTRING\nDimension: XY\nBounding box: xmin: -122.4083 ymin: 37.7838 xmax: -122.3945 ymax: 37.80097\nGeodetic CRS: WGS 84\n flow_id dest orig straight_dist street_dist total_down total_up trips15\n1 39-41 41 39 1452.201 1804.1150 11.205753 4.698162 68\n2 39-42 42 39 1734.861 2069.1557 10.290236 2.897886 23\n3 39-45 45 39 1255.349 1747.9928 11.015596 4.593927 83\n4 39-46 46 39 1323.303 1490.8361 3.511543 5.038044 258\n5 39-47 47 39 715.689 769.9189 0.000000 3.282495 127\n6 39-48 48 39 1996.778 2740.1290 11.375186 3.841296 81\n trips16 geometry\n1 68 LINESTRING (-122.4083 37.78...\n2 29 LINESTRING (-122.4083 37.78...\n3 50 LINESTRING (-122.4083 37.78...\n4 163 LINESTRING (-122.4083 37.78...\n5 73 LINESTRING (-122.4083 37.78...\n6 56 LINESTRING (-122.4083 37.78...\n\n\nwhere orig and dest are the station IDs of the origin and destination, street/straight_dist is the distance in metres between stations measured along the street network or as-the-crow-flies, total_down/up is the total downhil and climb in the trip, and tripsXX contains the amount of trips undertaken in the years of study." + "section": "\n5.2 Data", + "text": "5.2 Data\nIn this note, we will use data from the city of San Francisco representing bike trips on their public bike share system. The original source is the SF Open Data portal (link) and the dataset comprises both the location of each station in the Bay Area as well as information on trips (station of origin to station of destination) undertaken in the system from September 2014 to August 2015 and the following year. Since this note is about modeling and not data preparation, a cleanly reshaped version of the data, together with some additional information, has been created and placed in the sf_bikes folder. The data file is named flows.geojson and, in case you are interested, the (Python) code required to created from the original files in the SF Data Portal is also available on the flows_prep.ipynb notebook [url], also in the same folder.\nLet us then directly load the file with all the information necessary:\n\ndb <- st_read('./data/sf_bikes/flows.geojson')\n\nReading layer `flows' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/sf_bikes/flows.geojson' \n using driver `GeoJSON'\nSimple feature collection with 1722 features and 9 fields\nGeometry type: LINESTRING\nDimension: XY\nBounding box: xmin: -122.4237 ymin: 37.76914 xmax: -122.3875 ymax: 37.80737\nGeodetic CRS: WGS 84\n\n\nNote how the interface is slightly different since we are reading a GeoJSON file instead of a shapefile.\nThe data contains the geometries of the flows, as calculated from the Google Maps API, as well as a series of columns with characteristics of each flow:\n\nhead(db)\n\nSimple feature collection with 6 features and 9 fields\nGeometry type: LINESTRING\nDimension: XY\nBounding box: xmin: -122.4083 ymin: 37.7838 xmax: -122.3945 ymax: 37.80097\nGeodetic CRS: WGS 84\n flow_id dest orig straight_dist street_dist total_down total_up trips15\n1 39-41 41 39 1452.201 1804.1150 11.205753 4.698162 68\n2 39-42 42 39 1734.861 2069.1557 10.290236 2.897886 23\n3 39-45 45 39 1255.349 1747.9928 11.015596 4.593927 83\n4 39-46 46 39 1323.303 1490.8361 3.511543 5.038044 258\n5 39-47 47 39 715.689 769.9189 0.000000 3.282495 127\n6 39-48 48 39 1996.778 2740.1290 11.375186 3.841296 81\n trips16 geometry\n1 68 LINESTRING (-122.4083 37.78...\n2 29 LINESTRING (-122.4083 37.78...\n3 50 LINESTRING (-122.4083 37.78...\n4 163 LINESTRING (-122.4083 37.78...\n5 73 LINESTRING (-122.4083 37.78...\n6 56 LINESTRING (-122.4083 37.78...\n\n\nwhere orig and dest are the station IDs of the origin and destination, street/straight_dist is the distance in metres between stations measured along the street network or as-the-crow-flies, total_down/up is the total downhil and climb in the trip, and tripsXX contains the amount of trips undertaken in the years of study.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { "objectID": "05-flows.html#seeing-flows", "href": "05-flows.html#seeing-flows", "title": "5  Spatial Interaction Modelling", - "section": "5.3 “Seeing” flows", - "text": "5.3 “Seeing” flows\nThe easiest way to get a quick preview of what the data looks like spatially is to make a simple plot:\n\nplot(db$geometry)\n\n\n\n\nPotential routes\n\n\n\n\nEqually, if we want to visualize a single route, we can simply subset the table. For example, to get the shape of the trip from station 39 to station 48, we can:\n\ndb %>% \n dplyr::filter(orig == 39 & dest == 48) %>% \n ggplot(data = .) + \n geom_sf(color = \"black\", \n size = 0.1) +\n theme_void()\n\n\n\n\nTrip from station 39 to 48\n\n\n\n\nor, for the most popular route, we can:\n\nmost_pop <- db %>% \n dplyr::filter(trips15 == max(trips15))\n\nThese however do not reveal a lot: there is no geographical context (why are there so many routes along the NE?) and no sense of how volumes of bikers are allocated along different routes. Let us fix those two.\nThe easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using ggmap:\n\nbbox_db <- st_bbox(db)\nnames(bbox_db) <- c(\"left\", \"bottom\", \"right\", \"top\")\n\nSanFran <- get_stamenmap(\n bbox_db, \n zoom = 14, \n maptype = \"toner-lite\"\n )\n\nand make sure it looks like we intend it to look:\n\nggmap(SanFran)\n\n\n\n\nNow to combine tiles and routes, we need to pull out the coordinates that make up each line. For the route example above, this would be:\n\nxys1 <- as.data.frame(st_coordinates(most_pop))\n\nNow we can plot the route1 (note we also dim down the background to focus the attention on flows):\n\nggmap(SanFran, darken=0.5) + \n geom_path(\n aes(x=X, y=Y), \n data=xys1,\n size=1,\n color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784),\n lineend='round'\n )\n\nWarning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.\nℹ Please use `linewidth` instead.\n\n\n\n\n\nNow we can plot all of the lines by using a short for loop to build up the table:\n\n# Set up shell data.frame\nlines <- data.frame(\n lat = numeric(0), \n lon = numeric(0), \n trips = numeric(0),\n id = numeric(0)\n)\n# Run loop\nfor(x in 1:nrow(db)){\n # Pull out row\n r <- db[x, ]\n # Extract lon/lat coords\n xys <- as.data.frame(st_coordinates(r))\n names(xys) <- c('lon', 'lat')\n # Insert trips and id\n xys['trips'] <- r$trips15\n xys['id'] <- x\n # Append them to `lines`\n lines <- rbind(lines, xys)\n}\n\nNow we can go on and plot all of them:\n\nggmap(SanFran, darken=0.75) + \n geom_path(\n aes(x=lon, y=lat, group=id),\n data=lines,\n size=0.1,\n color=rgb(0.996078431372549, 0.7019607843137254, 0.03137254901960784),\n lineend='round'\n )\n\n\n\n\nFinally, we can get a sense of the distribution of the flows by associating a color gradient to each flow based on its number of trips:\n\nggmap(SanFran, darken=0.75) + \n geom_path(\n aes(x=lon, y=lat, group=id, colour=trips),\n data=lines,\n size=log1p(lines$trips / max(lines$trips)),\n lineend='round'\n ) +\n scale_colour_gradient(\n low='grey', high='#07eda0'\n ) +\n theme(\n axis.text.x = element_blank(),\n axis.text.y = element_blank(),\n axis.ticks = element_blank()\n )\n\n\n\n\nNote how we transform the size so it’s a proportion of the largest trip and then it is compressed with a logarithm." + "section": "\n5.3 “Seeing” flows", + "text": "5.3 “Seeing” flows\nThe easiest way to get a quick preview of what the data looks like spatially is to make a simple plot:\n\nplot(db$geometry)\n\n\n\n\n\n\n\nEqually, if we want to visualize a single route, we can simply subset the table. For example, to get the shape of the trip from station 39 to station 48, we can:\n\ndb %>% \n dplyr::filter(orig == 39 & dest == 48) %>% \n ggplot(data = .) + \n geom_sf(color = \"black\", \n size = 0.1) +\n theme_void()\n\n\n\n\n\n\n\nor, for the most popular route, we can:\n\nmost_pop <- db %>% \n dplyr::filter(trips15 == max(trips15))\n\nThese however do not reveal a lot: there is no geographical context (why are there so many routes along the NE?) and no sense of how volumes of bikers are allocated along different routes. Let us fix those two.\nThe easiest way to bring in geographical context is by overlaying the routes on top of a background map of tiles downloaded from the internet. Let us download this using basemapR:\n\n# create a bounding box\nbbox_db <- st_bbox(db)\n# download a basemap using ggplot and basemapR\nggplot() +\n base_map(bbox_db, increase_zoom = 2, basemap = \"positron\") +\n geom_sf(data = db, fill = NA, colour = \"transparent\") \n\n\n\n\n\n\n\nNow to combine tiles and routes, we need to pull out the coordinates that make up each line. For the route example above, this would be:\n\nxys1 <- as.data.frame(st_coordinates(most_pop))\n\nNow we can plot the route (note we also dim down the background to focus the attention on flows):\n\n\n\n\n\n\n\n\nTask\nCan you plot the route for the largest climb?\n\n\n\n\nggplot() +\n base_map(bbox_db, increase_zoom = 2, basemap = \"dark\") +\n geom_sf( data = db, fill = NA, colour = \"transparent\") +\n geom_path( data = xys1, \n aes( x = X, y = Y ), \n #size = 1,\n color = \"green\",\n lineend ='round'\n )\n\n\n\n\n\n\n\nNow we can plot all of the lines by using a short for loop to build up the table:\n\n# Set up shell data.frame\nlines <- data.frame(\n lat = numeric(0), \n lon = numeric(0), \n trips = numeric(0),\n id = numeric(0)\n)\n# Run loop\nfor(x in 1:nrow(db)){\n # Pull out row\n r <- db[x, ]\n # Extract lon/lat coords\n xys <- as.data.frame(st_coordinates(r))\n names(xys) <- c('lon', 'lat')\n # Insert trips and id\n xys['trips'] <- r$trips15\n xys['id'] <- x\n # Append them to `lines`\n lines <- rbind(lines, xys)\n}\n\nNow we can go on and plot all of them:\n\nggplot() +\n # call basemap\n base_map(bbox_db, increase_zoom = 2, basemap = \"dark\") +\n geom_sf(data = db, fill = NA, colour = \"transparent\") +\n # add data\n geom_path( data = lines, \n aes(x=lon, y=lat, \n group=id\n ), \n size = 1,\n color = \"green\",\n lineend = 'round')\n\n\n\n\n\n\n\nFinally, we can get a sense of the distribution of the flows by associating a color gradient to each flow based on its number of trips:\n\nggplot() +\n # call basemap\n base_map(bbox_db, increase_zoom = 2, basemap = \"dark\") +\n geom_sf(data = db, fill = NA, colour = \"transparent\") +\n # add flow data\n geom_path( data = lines, \n aes(x = lon, y = lat, group = id, colour = trips ), \n size=log1p(lines$trips / max(lines$trips)),\n lineend = 'round'\n ) +\n # create a colour palette\n scale_colour_gradient(\n low='#440154FF', high='#FDE725FF'\n ) +\n theme(\n axis.text.x = element_blank(),\n axis.text.y = element_blank(),\n axis.ticks = element_blank()\n )\n\n\n\n\n\n\n\nNote how we transform the size so it’s a proportion of the largest trip and then it is compressed with a logarithm.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { "objectID": "05-flows.html#modelling-flows", "href": "05-flows.html#modelling-flows", "title": "5  Spatial Interaction Modelling", - "section": "5.4 Modelling flows", - "text": "5.4 Modelling flows\nNow we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from Gelman and Hill (2006) such as predictive performance checks2 (PPC)\nBefore we start running regressions, let us first standardize the predictors so we can interpret the intercept as the average flow when all the predictors take the average value, and so we can interpret the model coefficients as changes in standard deviation units:\n\n# Scale all the table\ndb_std <- db %>% mutate(across(where(is.numeric), scale))\n\n# Reset trips as we want the original version\ndb_std$trips15 <- db$trips15\ndb_std$trips16 <- db$trips16\n\n# Reset origin and destination station and express them as factors\ndb_std$orig <- as.factor(db$orig)\ndb_std$dest <- as.factor(db$dest)\n\nBaseline model\nOne of the simplest possible models we can fit in this context is a linear model that explains the number of trips as a function of the straight distance between the two stations and total amount of climb and downhill. We will take this as the baseline on which we can further build later:\n\nm1 <- lm('trips15 ~ straight_dist + total_up + total_down', data=db_std)\nsummary(m1)\n\n\nCall:\nlm(formula = \"trips15 ~ straight_dist + total_up + total_down\", \n data = db_std)\n\nResiduals:\n Min 1Q Median 3Q Max \n-261.9 -168.3 -102.4 30.8 3527.4 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 182.070 8.110 22.451 < 2e-16 ***\nstraight_dist 17.906 9.108 1.966 0.0495 * \ntotal_up -44.100 9.353 -4.715 2.61e-06 ***\ntotal_down -20.241 9.229 -2.193 0.0284 * \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 336.5 on 1718 degrees of freedom\nMultiple R-squared: 0.02196, Adjusted R-squared: 0.02025 \nF-statistic: 12.86 on 3 and 1718 DF, p-value: 2.625e-08\n\n\nTo explore how good this model is, we will be comparing the predictions the model makes about the number of trips each flow should have with the actual number of trips. A first approach is to simply plot the distribution of both variables:\n\nplot(\n density(m1$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col=c('black', 'red'),\n lwd=1\n)\ntitle(main=\"Predictive check, point estimates - Baseline model\")\n\n\n\n\nThe plot makes pretty obvious that our initial model captures very few aspects of the distribution we want to explain. However, we should not get too attached to this plot just yet. What it is showing is the distribution of predicted point estimates from our model. Since our model is not deterministic but inferential, there is a certain degree of uncertainty attached to its predictions, and that is completely absent from this plot.\nGenerally speaking, a given model has two sources of uncertainty: predictive, and inferential. The former relates to the fact that the equation we fit does not capture all the elements or in the exact form they enter the true data generating process; the latter has to do with the fact that we never get to know the true value of the model parameters only guesses (estimates) subject to error and uncertainty. If you think of our linear model above as\n\\[\nT_{ij} = X_{ij}\\beta + \\epsilon_{ij}\n\\] where \\(T_{ij}\\) represents the number of trips undertaken between station \\(i\\) and \\(j\\), \\(X_{ij}\\) is the set of explanatory variables (length, climb, descent, etc.), and \\(\\epsilon_{ij}\\) is an error term assumed to be distributed as a normal distribution \\(N(0, \\sigma)\\); then predictive uncertainty comes from the fact that there are elements to some extent relevant for \\(y\\) that are not accounted for and thus subsummed into \\(\\epsilon_{ij}\\). Inferential uncertainty comes from the fact that we never get to know \\(\\beta\\) but only an estimate of it which is also subject to uncertainty itself.\nTaking these two sources into consideration means that the black line in the plot above represents only the behaviour of our model we expect if the error term is absent (no predictive uncertainty) and the coefficients are the true estimates (no inferential uncertainty). However, this is not necessarily the case as our estimate for the uncertainty of the error term is certainly not zero, and our estimates for each parameter are also subject to a great deal of inferential variability. We do not know to what extent other outcomes would be just as likely. Predictive checking relates to simulating several feasible scenarios under our model and use those to assess uncertainty and to get a better grasp of the quality of our predictions.\nTechnically speaking, to do this, we need to build a mechanism to obtain a possible draw from our model and then repeat it several times. The first part of those two steps can be elegantly dealt with by writing a short function that takes a given model and a set of predictors, and produces a possible random draw from such model:\n\ngenerate_draw <- function(m){\n # Set up predictors matrix\n x <- model.matrix(m)\n # Obtain draws of parameters (inferential uncertainty)\n sim_bs <- sim(m, 1)\n # Predicted value\n mu <- x %*% sim_bs@coef[1, ]\n # Draw\n n <- length(mu)\n y_hat <- rnorm(n, mu, sim_bs@sigma[1])\n return(y_hat)\n}\n\nThis function takes a model m and the set of covariates x used and returns a random realization of predictions from the model. To get a sense of how this works, we can get and plot a realization of the model, compared to the expected one and the actual values:\n\nnew_y <- generate_draw(m1)\n\nplot(\n density(m1$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m1$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlines(\n density(new_y), \n col='green',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual', 'Simulated'),\n col=c('black', 'red', 'green'),\n lwd=1\n)\n\n\n\n\nOnce we have this “draw engine”, we can set it to work as many times as we want using a simple for loop. In fact, we can directly plot these lines as compared to the expected one and the trip count:\n\nplot(\n density(m1$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m1$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='white',\n main=''\n)\n# Loop for realizations\nfor(i in 1:250){\n tmp_y <- generate_draw(m1)\n lines(density(tmp_y),\n col='grey',\n lwd=0.1\n )\n}\n#\nlines(\n density(m1$fitted.values), \n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Actual', 'Predicted', 'Simulated (n=250)'),\n col=c('red', 'black', 'grey'),\n lwd=1\n)\ntitle(main=\"Predictive check - Baseline model\")\n\n\n\n\nThe plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our “inferential engine”, which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model3 is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution.\nIt is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate4, our capabilities to infer about the more general underlying process are fairly limited.\nImproving the model\nThe bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular5. These three routes are aligned around the following principles:\n\nUse better approximations to model your dependent variable.\nRecognize the structure of your data.\nGet better predictors.\n\n\nUse better approximations to model your dependent variable\n\nStandard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for6; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal.\nOne of the most common distributions for this cases is the Poisson, which can be incorporated through a general linear model (or GLM). The underlying assumption here is that instead of \\(T_{ij} \\sim N(\\mu_{ij}, \\sigma)\\), our model now follows:\n\\[\nT_{ij} \\sim Poisson (\\exp^{X_{ij}\\beta})\n\\]\nAs usual, such a model is easy to run in R:\n\nm2 <- glm(\n 'trips15 ~ straight_dist + total_up + total_down', \n data=db_std,\n family=poisson,\n)\n\nNow let’s see how much better, if any, this approach is. To get a quick overview, we can simply plot the point predictions:\n\nplot(\n density(m2$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m2$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col=c('black', 'red'),\n lwd=1\n)\ntitle(main=\"Predictive check, point estimates - Poisson model\")\n\n\n\n\nTo incorporate uncertainty to these predictions, we need to tweak our generate_draw function so it accommodates the fact that our model is not linear anymore.\n\ngenerate_draw_poi <- function(m){\n # Set up predictors matrix\n x <- model.matrix(m)\n # Obtain draws of parameters (inferential uncertainty)\n sim_bs <- sim(m, 1)\n # Predicted value\n xb <- x %*% sim_bs@coef[1, ]\n #xb <- x %*% m$coefficients\n # Transform using the link function\n mu <- exp(xb)\n # Obtain a random realization\n y_hat <- rpois(n=length(mu), lambda=mu)\n return(y_hat)\n}\n\nAnd then we can examine both point predictions an uncertainty around them:\n\nplot(\n density(m2$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m2$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='white',\n main=''\n)\n# Loop for realizations\nfor(i in 1:250){\n tmp_y <- generate_draw_poi(m2)\n lines(\n density(tmp_y),\n col='grey',\n lwd=0.1\n )\n}\n#\nlines(\n density(m2$fitted.values), \n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual', 'Simulated (n=250)'),\n col=c('black', 'red', 'grey'),\n lwd=1\n)\ntitle(main=\"Predictive check - Poisson model\")\n\n\n\n\nVoila! Although the curve is still a bit off, centered too much to the right of the actual data, our predictive simulation leaves the fitted values right in the middle. This speaks to a better fit of the model to the actual distribution othe original data follow.\n\nRecognize the structure of your data\n\nSo far, we’ve treated our dataset as if it was flat (i.e. comprise of fully independent realizations) when in fact it is not. Most crucially, our baseline model does not account for the fact that every observation in the dataset pertains to a trip between two stations. This means that all the trips from or to the same station probably share elements which likely help explain how many trips are undertaken between stations. For example, think of trips to and from a station located in the famous Embarcadero, a popular tourist spot. Every route to and from there probably has more trips due to the popularity of the area and we are currently not acknowledging it in the model.\nA simple way to incorporate these effects into the model is through origin and destination fixed effects. This approach shares elements with both spatial fixed effects and multilevel modeling and essentially consists of including a binary variable for every origin and destination station. In mathematical notation, this equates to:\n\\[\nT_{ij} = X_{ij}\\beta + \\delta_i + \\delta_j + \\epsilon_{ij}\n\\]\nwhere \\(\\delta_i\\) and \\(\\delta_j\\) are origin and destination station fixed effects7, and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce orig and dest in the the model:\n\nm3 <- glm(\n 'trips15 ~ straight_dist + total_up + total_down + orig + dest', \n data=db_std,\n family=poisson\n)\n\nAnd with our new model, we can have a look at how well it does at predicting the overall number of trips8:\n\nplot(\n density(m3$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m3$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col=c('black', 'red'),\n lwd=1\n)\ntitle(main=\"Predictive check - Orig/dest FE Poisson model\")\n\n\n\n\nThat looks significantly better, doesn’t it? In fact, our model now better accounts for the long tail where a few routes take a lot of trips. This is likely because the distribution of trips is far from random across stations and our origin and destination fixed effects do a decent job at accounting for that structure. However our model is still notably underpredicting less popular routes and overpredicting routes with above average number of trips. Maybe we should think about moving beyond a simple linear model.\n\nGet better predictors\n\nThe final extension is, in principle, always available but, in practice, it can be tricky to implement. The core idea is that your baseline model might not have the best measurement of the phenomena you want to account for. In our example, we can think of the distance between stations. So far, we have been including the distance measured “as the crow flies” between stations. Although in some cases this is a good approximation (particularly when distances are long and likely route taken is as close to straight as possible), in some cases like ours, where the street layout and the presence of elevation probably matter more than the actual final distance pedalled, this is not necessarily a safe assumption.\nAs an exampe of this approach, we can replace the straight distance measurements for more refined ones based on the Google Maps API routes. This is very easy as all we need to do (once the distances have been calculated!) is to swap straight_dist for street_dist:\n\nm4 <- glm(\n 'trips15 ~ street_dist + total_up + total_down + orig + dest', \n data=db_std,\n family=poisson\n)\n\nAnd we can similarly get a sense of our predictive fitting with:\n\nplot(\n density(m4$fitted.values), \n xlim=c(-100, max(db_std$trips15)),\n ylim=c(0, max(c(\n max(density(m4$fitted.values)$y), \n max(density(db_std$trips15)$y)\n )\n )\n ),\n col='black',\n main=''\n)\nlines(\n density(db_std$trips15), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col=c('black', 'red'),\n lwd=1\n)\ntitle(main=\"Predictive check - Orig/dest FE Poisson model\")\n\n\n\n\nHard to tell any noticeable difference, right? To see if there is any, we can have a look at the estimates obtained:\n\nsummary(m4)$coefficients['street_dist', ]\n\n Estimate Std. Error z value Pr(>|z|) \n -9.961619e-02 2.688731e-03 -3.704952e+01 1.828096e-300 \n\n\nAnd compare this to that of the straight distances in the previous model:\n\nsummary(m3)$coefficients['straight_dist', ]\n\n Estimate Std. Error z value Pr(>|z|) \n -7.820014e-02 2.683052e-03 -2.914596e+01 9.399407e-187 \n\n\nAs we can see, the differences exist but they are not massive. Let’s use this example to learn how to interpret coefficients in a Poisson model9. Effectively, these estimates can be understood as multiplicative effects. Since our model fits\n\\[\nT_{ij} \\sim Poisson (\\exp^{X_{ij}\\beta})\n\\]\nwe need to transform \\(\\beta\\) through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is \\(\\beta_{street} = -0.0996\\), but this needs to be translated through the exponential into \\(e^{-0.0996} = 0.906\\). In other words, since distance is expressed in standard deviations10, we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with \\(e^{-0.0782} = 0.925\\) for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m)." + "section": "\n5.4 Modelling flows", + "text": "5.4 Modelling flows\nNow we have an idea of the spatial distribution of flows, we can begin to think about modeling them. The core idea in this section is to fit a model that can capture the particular characteristics of our variable of interest (the volume of trips) using a set of predictors that describe the nature of a given flow. We will start from the simplest model and then progressively build complexity until we get to a satisfying point. Along the way, we will be exploring each model using concepts from Gelman and Hill (2006) such as predictive performance checks1 (PPC)\n1 For a more elaborate introduction to PPC, have a look at Chapters 7 and 8.Before we start running regressions, let us first standardize the predictors so we can interpret the intercept as the average flow when all the predictors take the average value, and so we can interpret the model coefficients as changes in standard deviation units:\n\n# Scale all the table\ndb_std <- db %>% mutate(across(where(is.numeric), scale))\n\n# Reset trips as we want the original version\ndb_std$trips15 <- db$trips15\ndb_std$trips16 <- db$trips16\n\n# Reset origin and destination station and express them as factors\ndb_std$orig <- as.factor(db$orig)\ndb_std$dest <- as.factor(db$dest)\n\nBaseline model\nOne of the simplest possible models we can fit in this context is a linear model that explains the number of trips as a function of the straight distance between the two stations and total amount of climb and downhill. We will take this as the baseline on which we can further build later:\n\nm1 <- lm('trips15 ~ straight_dist + total_up + total_down', data=db_std)\nsummary(m1)\n\n\nCall:\nlm(formula = \"trips15 ~ straight_dist + total_up + total_down\", \n data = db_std)\n\nResiduals:\n Min 1Q Median 3Q Max \n-261.9 -168.3 -102.4 30.8 3527.4 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 182.070 8.110 22.451 < 2e-16 ***\nstraight_dist 17.906 9.108 1.966 0.0495 * \ntotal_up -44.100 9.353 -4.715 2.61e-06 ***\ntotal_down -20.241 9.229 -2.193 0.0284 * \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 336.5 on 1718 degrees of freedom\nMultiple R-squared: 0.02196, Adjusted R-squared: 0.02025 \nF-statistic: 12.86 on 3 and 1718 DF, p-value: 2.625e-08\n\n\nTo explore how good this model is, we will be comparing the predictions the model makes about the number of trips each flow should have with the actual number of trips. A first approach is to simply plot the distribution of both variables:\n\nplot(\n density( m1$fitted.values ), \n xlim = c(-100, max( db_std$trips15 )),\n main=''\n)\nlines(\n density( db_std$trips15 ), \n col='red',\n main=''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col=c('black', 'red'),\n lwd=1\n)\ntitle(main=\"Predictive check, point estimates - Baseline model\")\n\n\n\n\n\n\n\nThe plot makes pretty obvious that our initial model captures very few aspects of the distribution we want to explain. However, we should not get too attached to this plot just yet. What it is showing is the distribution of predicted point estimates from our model. Since our model is not deterministic but inferential, there is a certain degree of uncertainty attached to its predictions, and that is completely absent from this plot.\nGenerally speaking, a given model has two sources of uncertainty: predictive, and inferential. The former relates to the fact that the equation we fit does not capture all the elements or in the exact form they enter the true data generating process; the latter has to do with the fact that we never get to know the true value of the model parameters only guesses (estimates) subject to error and uncertainty. If you think of our linear model above as\n\\[\nT_{ij} = X_{ij}\\beta + \\epsilon_{ij}\n\\] where \\(T_{ij}\\) represents the number of trips undertaken between station \\(i\\) and \\(j\\), \\(X_{ij}\\) is the set of explanatory variables (length, climb, descent, etc.), and \\(\\epsilon_{ij}\\) is an error term assumed to be distributed as a normal distribution \\(N(0, \\sigma)\\); then predictive uncertainty comes from the fact that there are elements to some extent relevant for \\(y\\) that are not accounted for and thus subsummed into \\(\\epsilon_{ij}\\). Inferential uncertainty comes from the fact that we never get to know \\(\\beta\\) but only an estimate of it which is also subject to uncertainty itself.\nTaking these two sources into consideration means that the black line in the plot above represents only the behaviour of our model we expect if the error term is absent (no predictive uncertainty) and the coefficients are the true estimates (no inferential uncertainty). However, this is not necessarily the case as our estimate for the uncertainty of the error term is certainly not zero, and our estimates for each parameter are also subject to a great deal of inferential variability. We do not know to what extent other outcomes would be just as likely. Predictive checking relates to simulating several feasible scenarios under our model and use those to assess uncertainty and to get a better grasp of the quality of our predictions.\nTechnically speaking, to do this, we need to build a mechanism to obtain a possible draw from our model and then repeat it several times. The first part of those two steps can be elegantly dealt with by writing a short function that takes a given model and a set of predictors, and produces a possible random draw from such model:\n\ngenerate_draw <- function(m){\n # Set up predictors matrix\n x <- model.matrix( m )\n # Obtain draws of parameters (inferential uncertainty)\n sim_bs <- sim( m, 1)\n # Predicted value\n mu <- x %*% sim_bs@coef[1, ]\n # Draw\n n <- length( mu )\n y_hat <- rnorm( n, mu, sim_bs@sigma[1])\n return(y_hat)\n}\n\nThis function takes a model m and the set of covariates x used and returns a random realization of predictions from the model. To get a sense of how this works, we can get and plot a realization of the model, compared to the expected one and the actual values:\n\nnew_y <- generate_draw(m1)\n\nplot(\n density( m1$fitted.values ), \n xlim = c(-100, max( db_std$trips15 )),\n ylim = c(0, max(c(\n max( density( m1$fitted.values)$y ), \n max( density( db_std$trips15)$y )\n )\n )\n ),\n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlines(\n density( new_y ), \n col = 'green',\n main = ''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual', 'Simulated'),\n col = c( 'black', 'red', 'green' ),\n lwd = 1\n)\n\n\n\n\n\n\n\nOnce we have this “draw engine”, we can set it to work as many times as we want using a simple for loop. In fact, we can directly plot these lines as compared to the expected one and the trip count:\n\nplot(\n density( m1$fitted.values ), \n xlim = c(-100, max( db_std$trips15 )),\n ylim = c(0, max(c(\n max( density( m1$fitted.values)$y ), \n max( density( db_std$trips15)$y )\n )\n )\n ),\n col='white',\n main=''\n)\n# Loop for realizations\nfor(i in 1:250){\n tmp_y <- generate_draw( m1 )\n lines( density( tmp_y ),\n col = 'grey',\n lwd = 0.1\n )\n}\n#\nlines(\n density( m1$fitted.values ), \n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlegend(\n 'topright', \n c('Actual', 'Predicted', 'Simulated (n=250)'),\n col = c('red', 'black', 'grey'),\n lwd = 1\n)\ntitle(main=\"Predictive check - Baseline model\")\n\n\n\n\n\n\n\nThe plot shows there is a significant mismatch between the fitted values, which are much more concentrated around small positive values, and the realizations of our “inferential engine”, which depict a much less concentrated distribution of values. This is likely due to the combination of two different reasons: on the one hand, the accuracy of our estimates may be poor, causing them to jump around a wide range of potential values and hence resulting in very diverse predictions (inferential uncertainty); on the other hand, it may be that the amount of variation we are not able to account for in the model2 is so large that the degree of uncertainty contained in the error term of the model is very large, hence resulting in such a flat predictive distribution.\n2 The \\(R^2\\) of our model is around 2%3 which they are not really, in light of the comparison between the black and red lines.It is important to keep in mind that the issues discussed in the paragraph above relate only to the uncertainty behind our model, not to the point predictions derived from them, which are a mechanistic result of the minimization of the squared residuals and hence are not subject to probability or inference. That allows them in this case to provide a fitted distribution much more accurate apparently (black line above). However, the lesson to take from this model is that, even if the point predictions (fitted values) are artificially accurate3, our capabilities to infer about the more general underlying process are fairly limited.\nImproving the model\nThe bad news from the previous section is that our initial model is not great at explaining bike trips. The good news is there are several ways in which we can improve this. In this section we will cover three main extensions that exemplify three different routes you can take when enriching and augmenting models in general, and spatial interaction ones in particular4. These three routes are aligned around the following principles:\n4 These principles are general and can be applied to pretty much any modeling exercise you run into. The specific approaches we take in this note relate to spatial interaction models\nUse better approximations to model your dependent variable.\nRecognize the structure of your data.\nGet better predictors.\n\n\nUse better approximations to model your dependent variable\n\nStandard OLS regression assumes that the error term and, since the predictors are deterministic, the dependent variable are distributed following a normal (gaussian) distribution. This is usually a good approximation for several phenomena of interest, but maybe not the best one for trips along routes: for one, we know trips cannot be negative, which the normal distribution does not account for5; more subtly, their distribution is not really symmetric but skewed with a very long tail on the right. This is common in variables that represent counts and that is why usually it is more appropriate to fit a model that relies on a distribution different from the normal.\n5 For an illustration of this, consider the amount of probability mass to the left of zero in the predictive checks above.One of the most common distributions for this cases is the Poisson, which can be incorporated through a general linear model (or GLM). The underlying assumption here is that instead of \\(T_{ij} \\sim N(\\mu_{ij}, \\sigma)\\), our model now follows:\n\\[\nT_{ij} \\sim Poisson (\\exp^{X_{ij}\\beta})\n\\]\nAs usual, such a model is easy to run in R:\n\nm2 <- glm(\n 'trips15 ~ straight_dist + total_up + total_down', \n data=db_std,\n family=poisson,\n)\n\nNow let’s see how much better, if any, this approach is. To get a quick overview, we can simply plot the point predictions:\n\nplot(\n density( m2$fitted.values ), \n xlim = c( -100, max( db_std$trips15 )),\n ylim = c( 0, max(c(\n max( density( m2$fitted.values)$y ), \n max( density(db_std$trips15)$y )\n )\n )\n ),\n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col = c('black', 'red'),\n lwd = 1\n)\ntitle(main = \"Predictive check, point estimates - Poisson model\")\n\n\n\n\n\n\n\nTo incorporate uncertainty to these predictions, we need to tweak our generate_draw function so it accommodates the fact that our model is not linear anymore.\n\ngenerate_draw_poi <- function(m){\n # Set up predictors matrix\n x <- model.matrix( m )\n # Obtain draws of parameters (inferential uncertainty)\n sim_bs <- sim( m, 1 )\n # Predicted value\n xb <- x %*% sim_bs@coef[1, ]\n #xb <- x %*% m$coefficients\n # Transform using the link function\n mu <- exp( xb )\n # Obtain a random realization\n y_hat <- rpois( n = length( mu ), lambda = mu)\n return(y_hat)\n}\n\nAnd then we can examine both point predictions an uncertainty around them:\n\nplot(\n density( m2$fitted.values ), \n xlim = c(-100, max( db_std$trips15 )),\n ylim = c(0, max(c(\n max( density( m2$fitted.values)$y ), \n max( density( db_std$trips15)$y )\n )\n )\n ),\n col = 'white',\n main = ''\n)\n# Loop for realizations\nfor(i in 1:250){\n tmp_y <- generate_draw_poi( m2 )\n lines(\n density( tmp_y ),\n col = 'grey',\n lwd = 0.1\n )\n}\n#\nlines(\n density( m2$fitted.values ), \n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual', 'Simulated (n=250)'),\n col = c('black', 'red', 'grey'),\n lwd = 1\n)\ntitle( main = \"Predictive check - Poisson model\")\n\n\n\n\n\n\n\nVoila! Although the curve is still a bit off, centered too much to the right of the actual data, our predictive simulation leaves the fitted values right in the middle. This speaks to a better fit of the model to the actual distribution othe original data follow.\n\nRecognize the structure of your data\n\nSo far, we’ve treated our dataset as if it was flat (i.e. comprise of fully independent realizations) when in fact it is not. Most crucially, our baseline model does not account for the fact that every observation in the dataset pertains to a trip between two stations. This means that all the trips from or to the same station probably share elements which likely help explain how many trips are undertaken between stations. For example, think of trips to and from a station located in the famous Embarcadero, a popular tourist spot. Every route to and from there probably has more trips due to the popularity of the area and we are currently not acknowledging it in the model.\nA simple way to incorporate these effects into the model is through origin and destination fixed effects. This approach shares elements with both spatial fixed effects and multilevel modeling and essentially consists of including a binary variable for every origin and destination station. In mathematical notation, this equates to:\n\\[\nT_{ij} = X_{ij}\\beta + \\delta_i + \\delta_j + \\epsilon_{ij}\n\\]\nwhere \\(\\delta_i\\) and \\(\\delta_j\\) are origin and destination station fixed effects6, and the rest is as above. This strategy accounts for all the unobserved heterogeneity associated with the location of the station. Technically speaking, we simply need to introduce orig and dest in the the model:\n6 In this session, \\(\\delta_i\\) and \\(\\delta_j\\) are estimated as independent variables so their estimates are similar to interpret to those in \\(\\beta\\). An alternative approach could be to model them as random effects in a multilevel framework.\nm3 <- glm(\n 'trips15 ~ straight_dist + total_up + total_down + orig + dest', \n data = db_std,\n family = poisson\n)\n\nAnd with our new model, we can have a look at how well it does at predicting the overall number of trips7:\n7 Although, theoretically, we could also include simulations of the model in the plot to get a better sense of the uncertainty behind our model, in practice this seems troublesome. The problems most likely arise from the fact that many of the origin and destination binary variable coefficients are estimated with a great deal of uncertainty. This causes some of the simulation to generate extreme values that, when passed through the exponential term of the Poisson link function, cause problems. If anything, this is testimony of how a simple fixed effect model can sometimes lack accuracy and generate very uncertain estimates. A potential extension to work around these problems could be to fit a multilevel model with two specific levels beyond the trip-level: one for origin and another one for destination stations.\nplot(\n density( m3$fitted.values ), \n xlim = c(-100, max( db_std$trips15 )),\n ylim = c(0, max(c(\n max( density(m3$fitted.values)$y ), \n max( density(db_std$trips15)$y )\n )\n )\n ),\n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col = c('black', 'red'),\n lwd = 1\n)\ntitle( main = \"Predictive check - Orig/dest FE Poisson model\")\n\n\n\n\n\n\n\nThat looks significantly better, doesn’t it? In fact, our model now better accounts for the long tail where a few routes take a lot of trips. This is likely because the distribution of trips is far from random across stations and our origin and destination fixed effects do a decent job at accounting for that structure. However our model is still notably underpredicting less popular routes and overpredicting routes with above average number of trips. Maybe we should think about moving beyond a simple linear model.\n\nGet better predictors\n\nThe final extension is, in principle, always available but, in practice, it can be tricky to implement. The core idea is that your baseline model might not have the best measurement of the phenomena you want to account for. In our example, we can think of the distance between stations. So far, we have been including the distance measured “as the crow flies” between stations. Although in some cases this is a good approximation (particularly when distances are long and likely route taken is as close to straight as possible), in some cases like ours, where the street layout and the presence of elevation probably matter more than the actual final distance pedalled, this is not necessarily a safe assumption.\nAs an exampe of this approach, we can replace the straight distance measurements for more refined ones based on the Google Maps API routes. This is very easy as all we need to do (once the distances have been calculated!) is to swap straight_dist for street_dist:\n\nm4 <- glm(\n 'trips15 ~ street_dist + total_up + total_down + orig + dest', \n data = db_std,\n family = poisson\n)\n\nAnd we can similarly get a sense of our predictive fitting with:\n\nplot(\n density( m4$fitted.values ), \n xlim = c(-100, max( db_std$trips15)),\n ylim = c(0, max(c(\n max( density(m4$fitted.values)$y ), \n max( density(db_std$trips15)$y )\n )\n )\n ),\n col = 'black',\n main = ''\n)\nlines(\n density( db_std$trips15 ), \n col = 'red',\n main = ''\n)\nlegend(\n 'topright', \n c('Predicted', 'Actual'),\n col = c('black', 'red'),\n lwd = 1\n)\ntitle( main = \"Predictive check - Orig/dest FE Poisson model\")\n\n\n\n\n\n\n\nHard to tell any noticeable difference, right? To see if there is any, we can have a look at the estimates obtained:\n\nsummary(m4)$coefficients['street_dist', ]\n\n Estimate Std. Error z value Pr(>|z|) \n -9.961619e-02 2.688731e-03 -3.704952e+01 1.828096e-300 \n\n\nAnd compare this to that of the straight distances in the previous model:\n\nsummary(m3)$coefficients['straight_dist', ]\n\n Estimate Std. Error z value Pr(>|z|) \n -7.820014e-02 2.683052e-03 -2.914596e+01 9.399407e-187 \n\n\nAs we can see, the differences exist but they are not massive. Let’s use this example to learn how to interpret coefficients in a Poisson model8. Effectively, these estimates can be understood as multiplicative effects. Since our model fits\n8 See section 6.2 of Gelman and Hill (2006) for a similar treatment of these.\\[\nT_{ij} \\sim Poisson (\\exp^{X_{ij}\\beta})\n\\]\nwe need to transform \\(\\beta\\) through an exponential in order to get a sense of the effect of distance on the number of trips. This means that for the street distance, our original estimate is \\(\\beta_{street} = -0.0996\\), but this needs to be translated through the exponential into \\(e^{-0.0996} = 0.906\\). In other words, since distance is expressed in standard deviations9, we can expect a 10% decrease in the number of trips for an increase of one standard deviation (about 1Km) in the distance between the stations. This can be compared with \\(e^{-0.0782} = 0.925\\) for the straight distances, or a reduction of about 8% the number of trips for every increase of a standard deviation (about 720m).\n9 Remember the transformation at the very beginning.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { "objectID": "05-flows.html#predicting-flows", "href": "05-flows.html#predicting-flows", "title": "5  Spatial Interaction Modelling", - "section": "5.5 Predicting flows", - "text": "5.5 Predicting flows\nSo far we have put all of our modeling efforts in understanding the model we fit and improving such model so it fits our data as closely as possible. This is essential in any modelling exercise but should be far from a stopping point. Once we’re confident our model is a decent representation of the data generating process, we can start exploiting it. In this section, we will cover one specific case that showcases how a fitted model can help: out-of-sample forecasts.\nIt is August 2015, and you have just started working as a data scientist for the bikeshare company that runs the San Francisco system. You join them as they’re planning for the next academic year and, in order to plan their operations (re-allocating vans, station maintenance, etc.), they need to get a sense of how many people are going to be pedalling across the city and, crucially, where they are going to be pedalling through. What can you do to help them?\nThe easiest approach is to say “well, a good guess for how many people will be going between two given stations this coming year is how many went through last year, isn’t it?”. This is one prediction approach. However, you could see how, even if the same process governs over both datasets (2015 and 2016), each year will probably have some idiosyncracies and thus looking too closely into one year might not give the best possible answer for the next one. Ideally, you want a good stylized synthesis that captures the bits that stay constant over time and thus can be applied in the future and that ignores those aspects that are too particular to a given point in time. That is the rationale behind using a fitted model to obtain predictions.\nHowever good any theory though, the truth is in the pudding. So, to see if a modeling approach is better at producing forecasts than just using the counts from last year, we can put them to a test. The way this is done when evaluating the predictive performance of a model (as this is called in the literature) relies on two basic steps: a) obtain predictions from a given model and b) compare those to the actual values (in our case, with the counts for 2016 in trips16) and get a sense of “how off” they are. We have essentially covered a) above; for b), there are several measures to use. We will use one of the most common ones, the root mean squared error (RMSE), which roughly gives a sense of the average difference between a predicted vector and the real deal:\n\\[\nRMSE = \\sqrt{ \\sum_{ij} (\\hat{T_{ij}} - T_{ij})^2}\n\\]\nwhere \\(\\hat{T_{ij}}\\) is the predicted amount of trips between stations \\(i\\) and \\(j\\). RMSE is straightforward in R and, since we will use it a couple of times, let’s write a short function to make our lives easier:\n\nrmse <- function(t, p){\n se <- (t - p)^2\n mse <- mean(se)\n rmse <- sqrt(mse)\n return(rmse)\n}\n\nwhere t stands for the vector of true values, and p is the vector of predictions. Let’s give it a spin to make sure it works:\n\nrmse_m4 <- rmse(db_std$trips16, m4$fitted.values)\nrmse_m4\n\n[1] 256.2197\n\n\nThat means that, on average, predictions in our best model m4 are 256 trips off. Is this good? Bad? Worse? It’s hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year’s counts:11\n\nrmses <- data.frame(\n model=c(\n 'OLS', \n 'Poisson', \n 'Poisson + FE', \n 'Poisson + FE + street dist.',\n 'Trips-2015'\n ),\n RMSE=c(\n rmse(db_std$trips16, m1$fitted.values),\n rmse(db_std$trips16, m2$fitted.values),\n rmse(db_std$trips16, m3$fitted.values),\n rmse(db_std$trips16, m4$fitted.values),\n rmse(db_std$trips16, db_std$trips15)\n )\n)\nrmses\n\n model RMSE\n1 OLS 323.6135\n2 Poisson 320.8962\n3 Poisson + FE 254.4468\n4 Poisson + FE + street dist. 256.2197\n5 Trips-2015 131.0228\n\n\nThe table is both encouraging and disheartning at the same time. On the one hand, all the modeling techniques covered above behave as we would expect: the baseline model displays the worst predicting power of all, and every improvement (except the street distances!) results in notable decreases of the RMSE. This is good news. However, on the other hand, all of our modelling efforts fall short of given a better guess than simply using the previous year’s counts. Why? Does this mean that we should not pay attention to modeling and inference? Not really. Generally speaking, a model is as good at predicting as it is able to mimic the underlying process that gave rise to the data in the first place. The results above point to a case where our model is not picking up all the factors that determine the amount of trips undertaken in a give route. This could be improved by enriching the model with more/better predictors, as we have seen above. Also, the example above seems to point to a case where those idiosyncracies in 2015 that the model does not pick up seem to be at work in 2016 as well. This is great news for our prediction efforts this time, but we have no idea why this is the case and, for all that matters, it could change the coming year. Besides the elegant quantification of uncertainty, the true advantage of a modeling approach in this context is that, if well fit, it is able to pick up the fundamentals that apply over and over. This means that, if next year we’re not as lucky as this one and previous counts are not good predictors but the variables we used in our model continue to have a role in determining the outcome, the data scientist should be luckier and hit a better prediction." + "section": "\n5.5 Predicting flows", + "text": "5.5 Predicting flows\nSo far we have put all of our modeling efforts in understanding the model we fit and improving such model so it fits our data as closely as possible. This is essential in any modelling exercise but should be far from a stopping point. Once we’re confident our model is a decent representation of the data generating process, we can start exploiting it. In this section, we will cover one specific case that showcases how a fitted model can help: out-of-sample forecasts.\nIt is August 2015, and you have just started working as a data scientist for the bikeshare company that runs the San Francisco system. You join them as they’re planning for the next academic year and, in order to plan their operations (re-allocating vans, station maintenance, etc.), they need to get a sense of how many people are going to be pedalling across the city and, crucially, where they are going to be pedalling through. What can you do to help them?\nThe easiest approach is to say “well, a good guess for how many people will be going between two given stations this coming year is how many went through last year, isn’t it?”. This is one prediction approach. However, you could see how, even if the same process governs over both datasets (2015 and 2016), each year will probably have some idiosyncracies and thus looking too closely into one year might not give the best possible answer for the next one. Ideally, you want a good stylized synthesis that captures the bits that stay constant over time and thus can be applied in the future and that ignores those aspects that are too particular to a given point in time. That is the rationale behind using a fitted model to obtain predictions.\nHowever good any theory though, the truth is in the pudding. So, to see if a modeling approach is better at producing forecasts than just using the counts from last year, we can put them to a test. The way this is done when evaluating the predictive performance of a model (as this is called in the literature) relies on two basic steps: a) obtain predictions from a given model and b) compare those to the actual values (in our case, with the counts for 2016 in trips16) and get a sense of “how off” they are. We have essentially covered a) above; for b), there are several measures to use. We will use one of the most common ones, the root mean squared error (RMSE), which roughly gives a sense of the average difference between a predicted vector and the real deal:\n\\[\nRMSE = \\sqrt{ \\sum_{ij} (\\hat{T_{ij}} - T_{ij})^2}\n\\]\nwhere \\(\\hat{T_{ij}}\\) is the predicted amount of trips between stations \\(i\\) and \\(j\\). RMSE is straightforward in R and, since we will use it a couple of times, let’s write a short function to make our lives easier:\n\nrmse <- function(t, p){\n se <- (t - p)^2\n mse <- mean(se)\n rmse <- sqrt(mse)\n return(rmse)\n}\n\nwhere t stands for the vector of true values, and p is the vector of predictions. Let’s give it a spin to make sure it works:\n\nrmse_m4 <- rmse(db_std$trips16, m4$fitted.values)\nrmse_m4\n\n[1] 256.2197\n\n\nThat means that, on average, predictions in our best model m4 are 256 trips off. Is this good? Bad? Worse? It’s hard to say but, being practical, what we can say is whether this better than our alternative. Let us have a look at the RMSE of the other models as well as that of simply plugging in last year’s counts:[^05-flows-11]\n\n\n\n\n\n\n\n\nTask\nCan you create a single plot that displays the distribution of the predicted values of the five different ways to predict trips in 2016 and the actual counts of trips?\n\n\n\n\nrmses <- data.frame(\n model=c(\n 'OLS', \n 'Poisson', \n 'Poisson + FE', \n 'Poisson + FE + street dist.',\n 'Trips-2015'\n ),\n RMSE=c(\n rmse(db_std$trips16, m1$fitted.values),\n rmse(db_std$trips16, m2$fitted.values),\n rmse(db_std$trips16, m3$fitted.values),\n rmse(db_std$trips16, m4$fitted.values),\n rmse(db_std$trips16, db_std$trips15)\n )\n)\nrmses\n\n model RMSE\n1 OLS 323.6135\n2 Poisson 320.8962\n3 Poisson + FE 254.4468\n4 Poisson + FE + street dist. 256.2197\n5 Trips-2015 131.0228\n\n\nThe table is both encouraging and disheartning at the same time. On the one hand, all the modeling techniques covered above behave as we would expect: the baseline model displays the worst predicting power of all, and every improvement (except the street distances!) results in notable decreases of the RMSE. This is good news. However, on the other hand, all of our modelling efforts fall short of given a better guess than simply using the previous year’s counts. Why? Does this mean that we should not pay attention to modeling and inference? Not really. Generally speaking, a model is as good at predicting as it is able to mimic the underlying process that gave rise to the data in the first place. The results above point to a case where our model is not picking up all the factors that determine the amount of trips undertaken in a give route. This could be improved by enriching the model with more/better predictors, as we have seen above. Also, the example above seems to point to a case where those idiosyncracies in 2015 that the model does not pick up seem to be at work in 2016 as well. This is great news for our prediction efforts this time, but we have no idea why this is the case and, for all that matters, it could change the coming year. Besides the elegant quantification of uncertainty, the true advantage of a modeling approach in this context is that, if well fit, it is able to pick up the fundamentals that apply over and over. This means that, if next year we’re not as lucky as this one and previous counts are not good predictors but the variables we used in our model continue to have a role in determining the outcome, the data scientist should be luckier and hit a better prediction.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { "objectID": "05-flows.html#questions", "href": "05-flows.html#questions", "title": "5  Spatial Interaction Modelling", - "section": "5.6 Questions", - "text": "5.6 Questions\nWe will be using again the Madrid AirBnb dataset: \n\nmad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\nThe columns to use here are:\n\nprice_usd: price expressed in USD\nlog1p_price_usd: logarithm of the price expressed in USD\naccommodates: number of people the property accommodates\nbathrooms: number of bathrooms the property includes\nbedrooms: number of bedrooms the property includes\nbeds: number of beds the property includes\n\nWith these data at hand, accomplish the following challenges:\n\nSet up a baseline regression model where you explain the price of a property as a function of its characteristics:\n\n\\[\nP_i = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nFit a parallel model that uses the log of price as dependent variable:\n\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nPerform a predictive check analysis of both models, discussing how they compare, which one you would prefer, and why\n\n\n\n\n\nFotheringham, A Stewart, and Morton E O’Kelly. 1989. Spatial Interaction Models: Formulations and Applications. Vol. 1. Kluwer Academic Publishers Dordrecht.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nRowe, Francisco, Robin Lovelace, and Adam Dennett. 2022. “Spatial Interaction Modelling: A Manifesto.” http://dx.doi.org/10.31219/osf.io/xcdms.\n\n\nSingleton, Alex. 2017. “Geographic Data Science for Urban Analytics.” http://www.alex-singleton.com/GDS_UA_2017/." + "section": "\n5.6 Questions", + "text": "5.6 Questions\nWe will be using again the Madrid AirBnb dataset: \n\nmad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\nThe columns to use here are:\n\n\nprice_usd: price expressed in USD\n\nlog1p_price_usd: logarithm of the price expressed in USD\n\naccommodates: number of people the property accommodates\n\nbathrooms: number of bathrooms the property includes\n\nbedrooms: number of bedrooms the property includes\n\nbeds: number of beds the property includes\n\nWith these data at hand, accomplish the following challenges:\n\nSet up a baseline regression model where you explain the price of a property as a function of its characteristics:\n\n\\[\nP_i = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nFit a parallel model that uses the log of price as dependent variable:\n\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nPerform a predictive check analysis of both models, discussing how they compare, which one you would prefer, and why\n\n\n\n\n\n\n\nFotheringham, A Stewart, and Morton E O’Kelly. 1989. Spatial Interaction Models: Formulations and Applications. Vol. 1. Kluwer Academic Publishers Dordrecht.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nRowe, Francisco, Robin Lovelace, and Adam Dennett. 2022. “Spatial Interaction Modelling: A Manifesto.” http://dx.doi.org/10.31219/osf.io/xcdms.\n\n\nSingleton, Alex. 2017. “Geographic Data Science for Urban Analytics.” http://www.alex-singleton.com/GDS_UA_2017/.", + "crumbs": [ + "5  Spatial Interaction Modelling" + ] }, { - "objectID": "06-spatial-econometrics.html#dependencies", - "href": "06-spatial-econometrics.html#dependencies", + "objectID": "06-spatial-econometrics.html", + "href": "06-spatial-econometrics.html", "title": "6  Spatial Econometrics", - "section": "6.1 Dependencies", - "text": "6.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n\n# Layout\nlibrary(tufte)\n# For pretty table\nlibrary(knitr)\n# For string parsing\nlibrary(stringr)\n# Spatial Data management\nlibrary(rgdal)\n# Pretty graphics\nlibrary(ggplot2)\n# Pretty maps\nlibrary(ggmap)\n# For all your interpolation needs\nlibrary(gstat)\n# For data manipulation\nlibrary(dplyr)\n# Spatial regression\nlibrary(spdep)\n\nBefore we start any analysis, let us set the path to the directory where we are working. We can easily do that with setwd(). Please replace in the following line the path to the folder where you have placed this file and where the house_transactions folder with the data lives.\n\nsetwd('.')" + "section": "", + "text": "6.1 Dependencies\nWe will rely on the following libraries in this section, all of them included in Section 1.4.1:\n# Data management\nlibrary(tidyverse)\n# Spatial Data management\nlibrary(sf)\n# For all your interpolation needs\nlibrary(gstat)\n# Spatial regression\nlibrary(spdep)", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#data", "href": "06-spatial-econometrics.html#data", "title": "6  Spatial Econometrics", - "section": "6.2 Data", - "text": "6.2 Data\nTo explore ideas in spatial regression, we will the set of Airbnb properties for San Diego (US), borrowed from the “Geographic Data Science with Python” book (see here for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region.\nLet us load the data:\n\ndb <- st_read('data/abb_sd/regression_db.geojson')\n\nReading layer `regression_db' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/abb_sd/regression_db.geojson' \n using driver `GeoJSON'\nSimple feature collection with 6110 features and 19 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2812 ymin: 32.57349 xmax: -116.9553 ymax: 33.08311\nGeodetic CRS: WGS 84\n\n\nThe table contains the followig variables:\n\nnames(db)\n\n [1] \"accommodates\" \"bathrooms\" \"bedrooms\" \n [4] \"beds\" \"neighborhood\" \"pool\" \n [7] \"d2balboa\" \"coastal\" \"price\" \n[10] \"log_price\" \"id\" \"pg_Apartment\" \n[13] \"pg_Condominium\" \"pg_House\" \"pg_Other\" \n[16] \"pg_Townhouse\" \"rt_Entire_home.apt\" \"rt_Private_room\" \n[19] \"rt_Shared_room\" \"geometry\" \n\n\nFor most of this chapter, we will be exploring determinants and strategies for modelling the price of a property advertised in AirBnb. To get a first taste of what this means, we can create a plot of prices within the area of San Diego:\n\ndb %>%\n ggplot(aes(color = price)) +\n geom_sf() + \n scale_color_viridis_c() +\n theme_void()" + "section": "\n6.2 Data", + "text": "6.2 Data\nTo explore ideas in spatial regression, we will the set of Airbnb properties for San Diego (US), borrowed from the “Geographic Data Science with Python” book (see here for more info on the dataset source). This covers the point location of properties advertised on the Airbnb website in the San Diego region.\nLet us load the data:\n\ndb <- st_read('data/abb_sd/regression_db.geojson')\n\nReading layer `regression_db' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/abb_sd/regression_db.geojson' \n using driver `GeoJSON'\nSimple feature collection with 6110 features and 19 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -117.2812 ymin: 32.57349 xmax: -116.9553 ymax: 33.08311\nGeodetic CRS: WGS 84\n\n\nThe table contains the followig variables:\n\nnames(db)\n\n [1] \"accommodates\" \"bathrooms\" \"bedrooms\" \n [4] \"beds\" \"neighborhood\" \"pool\" \n [7] \"d2balboa\" \"coastal\" \"price\" \n[10] \"log_price\" \"id\" \"pg_Apartment\" \n[13] \"pg_Condominium\" \"pg_House\" \"pg_Other\" \n[16] \"pg_Townhouse\" \"rt_Entire_home.apt\" \"rt_Private_room\" \n[19] \"rt_Shared_room\" \"geometry\" \n\n\nFor most of this chapter, we will be exploring determinants and strategies for modelling the price of a property advertised in AirBnb. To get a first taste of what this means, we can create a plot of prices within the area of San Diego:\n\ndb %>%\n ggplot(aes(color = price)) +\n geom_sf() + \n scale_color_viridis_c() +\n theme_void()", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#non-spatial-regression-a-refresh", "href": "06-spatial-econometrics.html#non-spatial-regression-a-refresh", "title": "6  Spatial Econometrics", - "section": "6.3 Non-spatial regression, a refresh", - "text": "6.3 Non-spatial regression, a refresh\nBefore we discuss how to explicitly include space into the linear regression framework, let us show how basic regression can be carried out in R, and how you can interpret the results. By no means is this a formal and complete introduction to regression so, if that is what you are looking for, the first part of Gelman and Hill (2006), in particular chapters 3 and 4, are excellent places to check out.\nThe core idea of linear regression is to explain the variation in a given (dependent) variable as a linear function of a series of other (explanatory) variables. For example, in our case, we may want to express/explain the price of a property advertised on AirBnb as a function of some of its characteristics, such as the number of people it accommodates, and how many bathrooms, bedrooms and beds it features. At the individual level, we can express this as:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nwhere \\(P_i\\) is the price of house \\(i\\), \\(Acc_i\\), \\(Bath_i\\), \\(Bedr_i\\) and \\(Beds_i\\) are the count of people it accommodates, bathrooms, bedrooms and beds that house \\(i\\) has, respectively. The parameters \\(\\beta_{1,2, 3, 4}\\) give us information about in which way and to what extent each variable is related to the price, and \\(\\alpha\\), the constant term, is the average house price when all the other variables are zero. The term \\(\\epsilon_i\\) is usually referred to as the “error” and captures elements that influence the price of a house but are not accounted for explicitly. We can also express this relation in matrix form, excluding subindices for \\(i\\) as:\n\\[\n\\log(P) = \\alpha + \\beta_1 Acc + \\beta_2 Bath + \\beta_3 Bedr + \\beta_4 Beds + \\epsilon\n\\] where each term can be interpreted in terms of vectors instead of scalars (wit the exception of the parameters \\((\\alpha, \\beta_{1, 2, 3, 4})\\), which are scalars). Note we are using the logarithm of the price, since this allows us to interpret the coefficients as roughly the percentage change induced by a unit increase in the explanatory variable of the estimate.\nRemember a regression can be seen as a multivariate extension of bivariate correlations. Indeed, one way to interpret the \\(\\beta_k\\) coefficients in the equation above is as the degree of correlation between the explanatory variable \\(k\\) and the dependent variable, keeping all the other explanatory variables constant. When you calculate simple bivariate correlations, the coefficient of a variable is picking up the correlation between the variables, but it is also subsuming into it variation associated with other correlated variables –also called confounding factors1. Regression allows you to isolate the distinct effect that a single variable has on the dependent one, once we control for those other variables.\nPractically speaking, running linear regressions in R is straightforward. For example, to fit the model specified in the equation above, we only need one line of code:\n\nm1 <- lm('log_price ~ accommodates + bathrooms + bedrooms + beds', db)\n\nWe use the command lm, for linear model, and specify the equation we want to fit using a string that relates the dependent variable (the log of the price, log_price) with a set of explanatory ones (accommodates, bathrooms, bedrooms, beds) by using a tilde ~ that is akin to the \\(=\\) symbol in the mathematical equation above. Since we are using names of variables that are stored in a table, we need to pass the table object (db) as well.\nIn order to inspect the results of the model, the quickest way is to call summary:\n\nsummary(m1)\n\n\nCall:\nlm(formula = \"log_price ~ accommodates + bathrooms + bedrooms + beds\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.8486 -0.3234 -0.0095 0.3023 3.3975 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 4.018133 0.013947 288.10 <2e-16 ***\naccommodates 0.176851 0.005323 33.23 <2e-16 ***\nbathrooms 0.150981 0.012526 12.05 <2e-16 ***\nbedrooms 0.111700 0.012537 8.91 <2e-16 ***\nbeds -0.076974 0.007927 -9.71 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.5366 on 6105 degrees of freedom\nMultiple R-squared: 0.5583, Adjusted R-squared: 0.558 \nF-statistic: 1929 on 4 and 6105 DF, p-value: < 2.2e-16\n\n\nA full detailed explanation of the output is beyond the scope of the chapter, but we will highlight the relevant bits for our main purpose. This is concentrated on the Coefficients section, which gives us the estimates for the \\(\\beta_k\\) coefficients in our model. These estimates are the raw equivalent of the correlation coefficient between each explanatory variable and the dependent one, once the “polluting” effect of the other variables included in the model has been accounted for2. Results are as expected for the most part: houses tend to be significantly more expensive if they accommodate more people (an extra person increases the price by 17.7%, approximately), have more bathrooms (15.1%), or bedrooms (11.2%). Perhaps counter intuitively, an extra bed available seems to decrease the price by about -7.7%. However, keep in mind that this is the case, everything else equal. Hence, more beds per room and bathroom (ie. a more crowded house) is a bit cheaper." + "section": "\n6.3 Non-spatial regression, a refresh", + "text": "6.3 Non-spatial regression, a refresh\nBefore we discuss how to explicitly include space into the linear regression framework, let us show how basic regression can be carried out in R, and how you can interpret the results. By no means is this a formal and complete introduction to regression so, if that is what you are looking for, the first part of Gelman and Hill (2006), in particular chapters 3 and 4, are excellent places to check out.\nThe core idea of linear regression is to explain the variation in a given (dependent) variable as a linear function of a series of other (explanatory) variables. For example, in our case, we may want to express/explain the price of a property advertised on AirBnb as a function of some of its characteristics, such as the number of people it accommodates, and how many bathrooms, bedrooms and beds it features. At the individual level, we can express this as:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nwhere \\(P_i\\) is the price of house \\(i\\), \\(Acc_i\\), \\(Bath_i\\), \\(Bedr_i\\) and \\(Beds_i\\) are the count of people it accommodates, bathrooms, bedrooms and beds that house \\(i\\) has, respectively. The parameters \\(\\beta_{1,2, 3, 4}\\) give us information about in which way and to what extent each variable is related to the price, and \\(\\alpha\\), the constant term, is the average house price when all the other variables are zero. The term \\(\\epsilon_i\\) is usually referred to as the “error” and captures elements that influence the price of a house but are not accounted for explicitly. We can also express this relation in matrix form, excluding subindices for \\(i\\) as:\n\\[\n\\log(P) = \\alpha + \\beta_1 Acc + \\beta_2 Bath + \\beta_3 Bedr + \\beta_4 Beds + \\epsilon\n\\] where each term can be interpreted in terms of vectors instead of scalars (wit the exception of the parameters \\((\\alpha, \\beta_{1, 2, 3, 4})\\), which are scalars). Note we are using the logarithm of the price, since this allows us to interpret the coefficients as roughly the percentage change induced by a unit increase in the explanatory variable of the estimate.\nRemember a regression can be seen as a multivariate extension of bivariate correlations. Indeed, one way to interpret the \\(\\beta_k\\) coefficients in the equation above is as the degree of correlation between the explanatory variable \\(k\\) and the dependent variable, keeping all the other explanatory variables constant. When you calculate simple bivariate correlations, the coefficient of a variable is picking up the correlation between the variables, but it is also subsuming into it variation associated with other correlated variables –also called confounding factors[^06-spatial-econometrics-1]. Regression allows you to isolate the distinct effect that a single variable has on the dependent one, once we control for those other variables.\n\n\n\n\n\n\n\n\nTask\nAssume that new houses tend to be built more often in areas with low deprivation. If that is the case, then \\(NEW\\) and \\(IMD\\) will be correlated with each other (as well as with the price of a house, as we are hypothesizing in this case). If we calculate a simple correlation between \\(P\\) and \\(IMD\\), the coefficient will represent the degree of association between both variables, but it will also include some of the association between \\(IMD\\) and \\(NEW\\). That is, part of the obtained correlation coefficient will be due not to the fact that higher prices tend to be found in areas with low IMD, but to the fact that new houses tend to be more expensive. This is because (in this example) new houses tend to be built in areas with low deprivation and simple bivariate correlation cannot account for that.\n\n\n\nPractically speaking, running linear regressions in R is straightforward. For example, to fit the model specified in the equation above, we only need one line of code:\n\nm1 <- lm('log_price ~ accommodates + bathrooms + bedrooms + beds', db)\n\nWe use the command lm, for linear model, and specify the equation we want to fit using a string that relates the dependent variable (the log of the price, log_price) with a set of explanatory ones (accommodates, bathrooms, bedrooms, beds) by using a tilde ~ that is akin to the \\(=\\) symbol in the mathematical equation above. Since we are using names of variables that are stored in a table, we need to pass the table object (db) as well.\nIn order to inspect the results of the model, the quickest way is to call summary:\n\nsummary(m1)\n\n\nCall:\nlm(formula = \"log_price ~ accommodates + bathrooms + bedrooms + beds\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.8486 -0.3234 -0.0095 0.3023 3.3975 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 4.018133 0.013947 288.10 <2e-16 ***\naccommodates 0.176851 0.005323 33.23 <2e-16 ***\nbathrooms 0.150981 0.012526 12.05 <2e-16 ***\nbedrooms 0.111700 0.012537 8.91 <2e-16 ***\nbeds -0.076974 0.007927 -9.71 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.5366 on 6105 degrees of freedom\nMultiple R-squared: 0.5583, Adjusted R-squared: 0.558 \nF-statistic: 1929 on 4 and 6105 DF, p-value: < 2.2e-16\n\n\nA full detailed explanation of the output is beyond the scope of the chapter, but we will highlight the relevant bits for our main purpose. This is concentrated on the Coefficients section, which gives us the estimates for the \\(\\beta_k\\) coefficients in our model. These estimates are the raw equivalent of the correlation coefficient between each explanatory variable and the dependent one, once the “polluting” effect of the other variables included in the model has been accounted for1. Results are as expected for the most part: houses tend to be significantly more expensive if they accommodate more people (an extra person increases the price by 17.7%, approximately), have more bathrooms (15.1%), or bedrooms (11.2%). Perhaps counter intuitively, an extra bed available seems to decrease the price by about -7.7%. However, keep in mind that this is the case, everything else equal. Hence, more beds per room and bathroom (ie. a more crowded house) is a bit cheaper.\n1 Keep in mind that regression is no magic. We are only discounting the effect of other confounding factors that we include in the model, not of all potentially confounding factors.", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#spatial-regression-a-very-first-dip", "href": "06-spatial-econometrics.html#spatial-regression-a-very-first-dip", "title": "6  Spatial Econometrics", - "section": "6.4 Spatial regression: a (very) first dip", - "text": "6.4 Spatial regression: a (very) first dip\nSpatial regression is about explicitly introducing space or geographical context into the statistical framework of a regression. Conceptually, we want to introduce space into our model whenever we think it plays an important role in the process we are interested in, or when space can act as a reasonable proxy for other factors we cannot but should include in our model. As an example of the former, we can imagine how houses at the seafront are probably more expensive than those in the second row, given their better views. To illustrate the latter, we can think of how the character of a neighborhood is important in determining the price of a house; however, it is very hard to identify and quantify “character” per se, although it might be easier to get at its spatial variation, hence a case of space as a proxy.\nSpatial regression is a large field of development in the econometrics and statistics literature. In this brief introduction, we will consider two related but very different processes that give rise to spatial effects: spatial heterogeneity and spatial dependence. For more rigorous treatments of the topics introduced here, the reader is referred to Anselin (2003), Anselin and Rey (2014), and Gibbons, Overman, and Patacchini (2014)." + "section": "\n6.4 Spatial regression: a (very) first dip", + "text": "6.4 Spatial regression: a (very) first dip\nSpatial regression is about explicitly introducing space or geographical context into the statistical framework of a regression. Conceptually, we want to introduce space into our model whenever we think it plays an important role in the process we are interested in, or when space can act as a reasonable proxy for other factors we cannot but should include in our model. As an example of the former, we can imagine how houses at the seafront are probably more expensive than those in the second row, given their better views. To illustrate the latter, we can think of how the character of a neighborhood is important in determining the price of a house; however, it is very hard to identify and quantify “character” per se, although it might be easier to get at its spatial variation, hence a case of space as a proxy.\nSpatial regression is a large field of development in the econometrics and statistics literature. In this brief introduction, we will consider two related but very different processes that give rise to spatial effects: spatial heterogeneity and spatial dependence. For more rigorous treatments of the topics introduced here, the reader is referred to Anselin (2003), Anselin and Rey (2014), and Gibbons, Overman, and Patacchini (2014).", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#spatial-heterogeneity", "href": "06-spatial-econometrics.html#spatial-heterogeneity", "title": "6  Spatial Econometrics", - "section": "6.5 Spatial heterogeneity", - "text": "6.5 Spatial heterogeneity\nSpatial heterogeneity (SH) arises when we cannot safely assume the process we are studying operates under the same “rules” throughout the geography of interest. In other words, we can observe SH when there are effects on the outcome variable that are intrinsically linked to specific locations. A good example of this is the case of seafront houses above: we are trying to model the price of a house and, the fact some houses are located under certain conditions (i.e. by the sea), makes their price behave differently. This somewhat abstract concept of SH can be made operational in a model in several ways. We will explore the following two: spatial fixed-effects (FE); and spatial regimes, which is a generalization of FE.\nSpatial FE\nLet us consider the house price example from the previous section to introduce a more general illustration that relates to the second motivation for spatial effects (“space as a proxy”). Given we are only including two explanatory variables in the model, it is likely we are missing some important factors that play a role at determining the price at which a house is sold. Some of them, however, are likely to vary systematically over space (e.g. different neighborhood characteristics). If that is the case, we can control for those unobserved factors by using traditional dummy variables but basing their creation on a spatial rule. For example, let us include a binary variable for every neighbourhood, as provided by AirBnB, indicating whether a given house is located within such area (1) or not (0). Neighbourhood membership is expressed on the neighborhood column:\n\ndb %>%\n ggplot(aes(color = neighborhood)) +\n geom_sf() + \n theme_void()\n\n\n\n\nMathematically, we are now fitting the following equation:\n\\[\n\\log(P_i) = \\alpha_r + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nwhere the main difference is that we are now allowing the constant term, \\(\\alpha\\), to vary by neighbourhood \\(r\\), \\(\\alpha_r\\).\nProgrammatically, we can fit this model with lm:\n\n# Include `-1` to eliminate the constant term and include a dummy for every area\nm2 <- lm(\n 'log_price ~ neighborhood + accommodates + bathrooms + bedrooms + beds - 1', \n db\n)\nsummary(m2)\n\n\nCall:\nlm(formula = \"log_price ~ neighborhood + accommodates + bathrooms + bedrooms + beds - 1\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.4549 -0.2920 -0.0203 0.2741 3.5323 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \nneighborhoodBalboa Park 3.994775 0.036539 109.33 <2e-16 ***\nneighborhoodBay Ho 3.780025 0.086081 43.91 <2e-16 ***\nneighborhoodBay Park 3.941847 0.055788 70.66 <2e-16 ***\nneighborhoodCarmel Valley 4.034052 0.062811 64.23 <2e-16 ***\nneighborhoodCity Heights West 3.698788 0.065502 56.47 <2e-16 ***\nneighborhoodClairemont Mesa 3.658339 0.051438 71.12 <2e-16 ***\nneighborhoodCollege Area 3.649859 0.064979 56.17 <2e-16 ***\nneighborhoodCore 4.433447 0.058864 75.32 <2e-16 ***\nneighborhoodCortez Hill 4.294790 0.057648 74.50 <2e-16 ***\nneighborhoodDel Mar Heights 4.300659 0.060912 70.61 <2e-16 ***\nneighborhoodEast Village 4.241146 0.032019 132.46 <2e-16 ***\nneighborhoodGaslamp Quarter 4.473863 0.052493 85.23 <2e-16 ***\nneighborhoodGrant Hill 4.001481 0.058825 68.02 <2e-16 ***\nneighborhoodGrantville 3.664989 0.080168 45.72 <2e-16 ***\nneighborhoodKensington 4.073520 0.087322 46.65 <2e-16 ***\nneighborhoodLa Jolla 4.400145 0.026772 164.36 <2e-16 ***\nneighborhoodLa Jolla Village 4.066151 0.087263 46.60 <2e-16 ***\nneighborhoodLinda Vista 3.817940 0.063128 60.48 <2e-16 ***\nneighborhoodLittle Italy 4.390651 0.052433 83.74 <2e-16 ***\nneighborhoodLoma Portal 4.034473 0.036173 111.53 <2e-16 ***\nneighborhoodMarina 4.046133 0.052178 77.55 <2e-16 ***\nneighborhoodMidtown 4.032038 0.030280 133.16 <2e-16 ***\nneighborhoodMidtown District 4.356943 0.071756 60.72 <2e-16 ***\nneighborhoodMira Mesa 3.570523 0.061543 58.02 <2e-16 ***\nneighborhoodMission Bay 4.251309 0.023318 182.32 <2e-16 ***\nneighborhoodMission Valley 4.012410 0.083766 47.90 <2e-16 ***\nneighborhoodMoreno Mission 4.028288 0.063342 63.59 <2e-16 ***\nneighborhoodNormal Heights 3.791895 0.054730 69.28 <2e-16 ***\nneighborhoodNorth Clairemont 3.498107 0.076432 45.77 <2e-16 ***\nneighborhoodNorth Hills 3.959403 0.026823 147.61 <2e-16 ***\nneighborhoodNorthwest 3.810201 0.078158 48.75 <2e-16 ***\nneighborhoodOcean Beach 4.152695 0.032352 128.36 <2e-16 ***\nneighborhoodOld Town 4.127737 0.046523 88.72 <2e-16 ***\nneighborhoodOtay Ranch 3.722902 0.091633 40.63 <2e-16 ***\nneighborhoodPacific Beach 4.116749 0.022711 181.27 <2e-16 ***\nneighborhoodPark West 4.216829 0.050370 83.72 <2e-16 ***\nneighborhoodRancho Bernadino 3.873962 0.080780 47.96 <2e-16 ***\nneighborhoodRancho Penasquitos 3.772037 0.068808 54.82 <2e-16 ***\nneighborhoodRoseville 4.070468 0.065299 62.34 <2e-16 ***\nneighborhoodSan Carlos 3.935042 0.093205 42.22 <2e-16 ***\nneighborhoodScripps Ranch 3.641239 0.085190 42.74 <2e-16 ***\nneighborhoodSerra Mesa 3.912127 0.066630 58.71 <2e-16 ***\nneighborhoodSouth Park 3.987019 0.060141 66.30 <2e-16 ***\nneighborhoodUniversity City 3.772504 0.039638 95.17 <2e-16 ***\nneighborhoodWest University Heights 4.043161 0.048238 83.82 <2e-16 ***\naccommodates 0.150283 0.005086 29.55 <2e-16 ***\nbathrooms 0.132287 0.011886 11.13 <2e-16 ***\nbedrooms 0.147631 0.011960 12.34 <2e-16 ***\nbeds -0.074622 0.007405 -10.08 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.4971 on 6061 degrees of freedom\nMultiple R-squared: 0.9904, Adjusted R-squared: 0.9904 \nF-statistic: 1.28e+04 on 49 and 6061 DF, p-value: < 2.2e-16\n\n\nEconometrically speaking, what the postcode FE we have introduced imply is that, instead of comparing all house prices across San Diego as equal, we only derive variation from within each postcode. In our particular case, estimating spatial FE in our particular example also gives you an indirect measure of area desirability: since they are simple dummies in a regression explaining the price of a house, their estimate tells us about how much people are willing to pay to live in a given area. We can visualise this “geography of desirability” by plotting the estimates of each fixed effect on a map:\n\n# Extract neighborhood names from coefficients\nnei.names <- m2$coefficients %>%\n as.data.frame() %>%\n row.names() %>%\n str_replace(\"neighborhood\", \"\")\n# Set up as Data Frame\nnei.fes <- data.frame(\n coef = m2$coefficients,\n nei = nei.names,\n row.names = nei.names\n) %>%\n right_join(\n db, by = c(\"nei\" = \"neighborhood\")\n)\n# Plot\nnei.fes %>%\n st_as_sf() %>%\n ggplot(aes(color = coef)) +\n geom_sf() +\n scale_color_viridis_c() +\n theme_void()\n\n\n\n\nWe can see how neighborhoods in the left (west) tend to have higher prices. What we can’t see, but it is represented there if you are familiar with the geography of San Diego, is that the city is bounded by the Pacific ocean on the left, suggesting neighbourhoods by the beach tend to be more expensive.\nRemember that the interpretation of a \\(\\beta_k\\) coefficient is the effect of variable \\(k\\), given all the other explanatory variables included remain constant. By including a single variable for each area, we are effectively forcing the model to compare as equal only house prices that share the same value for each variable; in other words, only houses located within the same area. Introducing FE affords you a higher degree of isolation of the effects of the variables you introduce in your model because you can control for unobserved effects that align spatially with the distribution of the FE you introduce (by neighbourhood, in our case).\nSpatial regimes\nAt the core of estimating spatial FEs is the idea that, instead of assuming the dependent variable behaves uniformly over space, there are systematic effects following a geographical pattern that affect its behaviour. In other words, spatial FEs introduce econometrically the notion of spatial heterogeneity. They do this in the simplest possible form: by allowing the constant term to vary geographically. The other elements of the regression are left untouched and hence apply uniformly across space. The idea of spatial regimes (SRs) is to generalize the spatial FE approach to allow not only the constant term to vary but also any other explanatory variable. This implies that the equation we will be estimating is: \\[\n\\log(P_i) = \\alpha_r + \\beta_{1r} Acc_i + \\beta_{2r} Bath_i + \\beta_{3r} Bedr_i + \\beta_{4r} Beds_i + \\epsilon_i\n\\]\nwhere we are not only allowing the constant term to vary by region (\\(\\alpha_r\\)), but also every other parameter (\\(\\beta_{kr}\\)).\nAlso, given we are going to allow every coefficient to vary by regime, we will need to explicitly set a constant term that we can allow to vary:\n\ndb$one <- 1\n\nThen, the estimation leverages the capabilities in model description of R formulas:\n\n# `:` notation implies interaction variables\nm3 <- lm(\n 'log_price ~ 0 + (accommodates + bathrooms + bedrooms + beds):(neighborhood)', \n db\n)\nsummary(m3)\n\n\nCall:\nlm(formula = \"log_price ~ 0 + (accommodates + bathrooms + bedrooms + beds):(neighborhood)\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-10.4790 -0.0096 1.0931 1.7599 6.1073 \n\nCoefficients:\n Estimate Std. Error t value\naccommodates:neighborhoodBalboa Park 0.063528 0.093237 0.681\naccommodates:neighborhoodBay Ho -0.259615 0.335007 -0.775\naccommodates:neighborhoodBay Park -0.355401 0.232720 -1.527\naccommodates:neighborhoodCarmel Valley 0.129786 0.187193 0.693\naccommodates:neighborhoodCity Heights West 0.447371 0.231998 1.928\naccommodates:neighborhoodClairemont Mesa 0.711353 0.177821 4.000\naccommodates:neighborhoodCollege Area -0.346152 0.188071 -1.841\naccommodates:neighborhoodCore 0.125864 0.148417 0.848\naccommodates:neighborhoodCortez Hill 0.715958 0.126562 5.657\naccommodates:neighborhoodDel Mar Heights 0.829195 0.214067 3.874\naccommodates:neighborhoodEast Village 0.214642 0.077394 2.773\naccommodates:neighborhoodGaslamp Quarter 0.451443 0.197637 2.284\naccommodates:neighborhoodGrant Hill 1.135176 0.167771 6.766\naccommodates:neighborhoodGrantville 0.300907 0.280369 1.073\naccommodates:neighborhoodKensington 0.668742 0.450243 1.485\naccommodates:neighborhoodLa Jolla 0.520882 0.055887 9.320\naccommodates:neighborhoodLa Jolla Village 0.566452 0.413185 1.371\naccommodates:neighborhoodLinda Vista 0.523975 0.282219 1.857\naccommodates:neighborhoodLittle Italy 0.603908 0.121899 4.954\naccommodates:neighborhoodLoma Portal 0.487743 0.127870 3.814\naccommodates:neighborhoodMarina 0.431384 0.172628 2.499\naccommodates:neighborhoodMidtown 0.618058 0.087992 7.024\naccommodates:neighborhoodMidtown District 0.430398 0.191682 2.245\naccommodates:neighborhoodMira Mesa -0.018199 0.310167 -0.059\naccommodates:neighborhoodMission Bay 0.440951 0.049454 8.916\naccommodates:neighborhoodMission Valley 0.144530 0.507925 0.285\naccommodates:neighborhoodMoreno Mission 0.100471 0.229460 0.438\naccommodates:neighborhoodNormal Heights 0.413682 0.198584 2.083\naccommodates:neighborhoodNorth Clairemont -0.242723 0.307090 -0.790\naccommodates:neighborhoodNorth Hills 0.262840 0.083258 3.157\naccommodates:neighborhoodNorthwest -0.229157 0.255656 -0.896\naccommodates:neighborhoodOcean Beach 0.754771 0.079097 9.542\naccommodates:neighborhoodOld Town 0.177176 0.159714 1.109\naccommodates:neighborhoodOtay Ranch -0.333536 0.309545 -1.078\naccommodates:neighborhoodPacific Beach 0.345475 0.057599 5.998\naccommodates:neighborhoodPark West 0.909020 0.156013 5.827\naccommodates:neighborhoodRancho Bernadino -0.118939 0.256750 -0.463\naccommodates:neighborhoodRancho Penasquitos 0.121845 0.228456 0.533\naccommodates:neighborhoodRoseville 0.316929 0.226110 1.402\naccommodates:neighborhoodSan Carlos 0.191248 0.318706 0.600\naccommodates:neighborhoodScripps Ranch 0.347638 0.127239 2.732\naccommodates:neighborhoodSerra Mesa 0.495491 0.282281 1.755\naccommodates:neighborhoodSouth Park 0.334378 0.256708 1.303\naccommodates:neighborhoodUniversity City 0.107605 0.113883 0.945\naccommodates:neighborhoodWest University Heights 0.190215 0.212040 0.897\nbathrooms:neighborhoodBalboa Park 2.275321 0.225032 10.111\nbathrooms:neighborhoodBay Ho 3.312231 0.530568 6.243\nbathrooms:neighborhoodBay Park 2.231649 0.365655 6.103\nbathrooms:neighborhoodCarmel Valley 1.191058 0.224138 5.314\nbathrooms:neighborhoodCity Heights West 2.517235 0.550272 4.575\nbathrooms:neighborhoodClairemont Mesa 3.737297 0.427366 8.745\nbathrooms:neighborhoodCollege Area 3.370263 0.413479 8.151\nbathrooms:neighborhoodCore 3.635188 0.490640 7.409\nbathrooms:neighborhoodCortez Hill 1.631032 0.299654 5.443\nbathrooms:neighborhoodDel Mar Heights 1.346206 0.342828 3.927\nbathrooms:neighborhoodEast Village 2.600489 0.190932 13.620\nbathrooms:neighborhoodGaslamp Quarter 3.183092 0.527615 6.033\nbathrooms:neighborhoodGrant Hill 2.770976 0.416838 6.648\nbathrooms:neighborhoodGrantville 2.177175 0.693599 3.139\nbathrooms:neighborhoodKensington 1.284044 0.671482 1.912\nbathrooms:neighborhoodLa Jolla 0.852667 0.099413 8.577\nbathrooms:neighborhoodLa Jolla Village 0.984426 1.193870 0.825\nbathrooms:neighborhoodLinda Vista 2.359895 0.393392 5.999\nbathrooms:neighborhoodLittle Italy 2.600567 0.275834 9.428\nbathrooms:neighborhoodLoma Portal 2.575164 0.249679 10.314\nbathrooms:neighborhoodMarina 3.317139 0.656533 5.053\nbathrooms:neighborhoodMidtown 0.899736 0.112205 8.019\nbathrooms:neighborhoodMidtown District 3.143440 0.594875 5.284\nbathrooms:neighborhoodMira Mesa 2.858280 0.512511 5.577\nbathrooms:neighborhoodMission Bay 1.764929 0.122421 14.417\nbathrooms:neighborhoodMission Valley 2.666000 1.365483 1.952\nbathrooms:neighborhoodMoreno Mission 3.234512 0.557898 5.798\nbathrooms:neighborhoodNormal Heights 3.505139 0.467965 7.490\nbathrooms:neighborhoodNorth Clairemont 2.574847 0.613471 4.197\nbathrooms:neighborhoodNorth Hills 2.584724 0.191541 13.494\nbathrooms:neighborhoodNorthwest 2.877519 0.569924 5.049\nbathrooms:neighborhoodOcean Beach 1.702208 0.207508 8.203\nbathrooms:neighborhoodOld Town 2.249120 0.302755 7.429\nbathrooms:neighborhoodOtay Ranch 2.818736 1.132794 2.488\nbathrooms:neighborhoodPacific Beach 2.272803 0.130607 17.402\nbathrooms:neighborhoodPark West 2.676739 0.308257 8.683\nbathrooms:neighborhoodRancho Bernadino 0.856723 0.555198 1.543\nbathrooms:neighborhoodRancho Penasquitos 0.677767 0.414569 1.635\nbathrooms:neighborhoodRoseville 1.109625 0.360103 3.081\nbathrooms:neighborhoodSan Carlos 2.489815 0.511232 4.870\nbathrooms:neighborhoodScripps Ranch 2.459862 0.469601 5.238\nbathrooms:neighborhoodSerra Mesa 2.968934 0.602807 4.925\nbathrooms:neighborhoodSouth Park 2.895471 0.521793 5.549\nbathrooms:neighborhoodUniversity City 3.125387 0.347825 8.986\nbathrooms:neighborhoodWest University Heights 2.188257 0.390408 5.605\nbedrooms:neighborhoodBalboa Park 0.605655 0.245384 2.468\nbedrooms:neighborhoodBay Ho 0.836163 0.631871 1.323\nbedrooms:neighborhoodBay Park 1.060944 0.430737 2.463\nbedrooms:neighborhoodCarmel Valley 0.521954 0.480497 1.086\nbedrooms:neighborhoodCity Heights West -0.272600 0.663983 -0.411\nbedrooms:neighborhoodClairemont Mesa -0.742539 0.450344 -1.649\nbedrooms:neighborhoodCollege Area -0.306621 0.410476 -0.747\nbedrooms:neighborhoodCore -0.786470 0.395991 -1.986\nbedrooms:neighborhoodCortez Hill 0.793039 0.380195 2.086\nbedrooms:neighborhoodDel Mar Heights -0.071069 0.369070 -0.193\nbedrooms:neighborhoodEast Village -0.186076 0.213572 -0.871\nbedrooms:neighborhoodGaslamp Quarter -0.294024 0.342057 -0.860\nbedrooms:neighborhoodGrant Hill -0.456825 0.425374 -1.074\nbedrooms:neighborhoodGrantville 0.907259 0.770945 1.177\nbedrooms:neighborhoodKensington -0.257195 1.009326 -0.255\nbedrooms:neighborhoodLa Jolla -0.152098 0.133726 -1.137\nbedrooms:neighborhoodLa Jolla Village 4.291700 1.882046 2.280\nbedrooms:neighborhoodLinda Vista -0.485372 0.642684 -0.755\nbedrooms:neighborhoodLittle Italy 0.057475 0.306357 0.188\nbedrooms:neighborhoodLoma Portal -0.406484 0.250607 -1.622\nbedrooms:neighborhoodMarina -0.831114 0.511626 -1.624\nbedrooms:neighborhoodMidtown 0.696852 0.167900 4.150\nbedrooms:neighborhoodMidtown District 0.010614 0.509151 0.021\nbedrooms:neighborhoodMira Mesa -0.197692 0.780959 -0.253\nbedrooms:neighborhoodMission Bay -0.330540 0.121602 -2.718\nbedrooms:neighborhoodMission Valley 0.514998 1.295767 0.397\nbedrooms:neighborhoodMoreno Mission -0.584689 0.596044 -0.981\nbedrooms:neighborhoodNormal Heights -0.127744 0.391691 -0.326\nbedrooms:neighborhoodNorth Clairemont 0.281306 0.695297 0.405\nbedrooms:neighborhoodNorth Hills 0.380444 0.178477 2.132\nbedrooms:neighborhoodNorthwest 0.288603 0.607295 0.475\nbedrooms:neighborhoodOcean Beach -0.038069 0.207927 -0.183\nbedrooms:neighborhoodOld Town -0.319724 0.375203 -0.852\nbedrooms:neighborhoodOtay Ranch 0.015564 1.332279 0.012\nbedrooms:neighborhoodPacific Beach -0.037912 0.139026 -0.273\nbedrooms:neighborhoodPark West -0.696514 0.413881 -1.683\nbedrooms:neighborhoodRancho Bernadino 1.034776 0.579798 1.785\nbedrooms:neighborhoodRancho Penasquitos 0.674520 0.519260 1.299\nbedrooms:neighborhoodRoseville 0.881011 0.592962 1.486\nbedrooms:neighborhoodSan Carlos -0.394191 0.540343 -0.730\nbedrooms:neighborhoodScripps Ranch 1.107455 0.336101 3.295\nbedrooms:neighborhoodSerra Mesa 0.253001 0.620774 0.408\nbedrooms:neighborhoodSouth Park -0.595844 0.407811 -1.461\nbedrooms:neighborhoodUniversity City 0.203783 0.455767 0.447\nbedrooms:neighborhoodWest University Heights 0.242873 0.359245 0.676\nbeds:neighborhoodBalboa Park 0.041556 0.173183 0.240\nbeds:neighborhoodBay Ho -0.402544 0.495241 -0.813\nbeds:neighborhoodBay Park 0.283958 0.410776 0.691\nbeds:neighborhoodCarmel Valley 0.150416 0.288268 0.522\nbeds:neighborhoodCity Heights West -0.217526 0.497878 -0.437\nbeds:neighborhoodClairemont Mesa -1.109581 0.308998 -3.591\nbeds:neighborhoodCollege Area 0.594892 0.312780 1.902\nbeds:neighborhoodCore 0.602559 0.277027 2.175\nbeds:neighborhoodCortez Hill -0.609996 0.143559 -4.249\nbeds:neighborhoodDel Mar Heights -0.708476 0.257299 -2.754\nbeds:neighborhoodEast Village 0.399909 0.148641 2.690\nbeds:neighborhoodGaslamp Quarter 0.240245 0.319910 0.751\nbeds:neighborhoodGrant Hill -1.315807 0.186724 -7.047\nbeds:neighborhoodGrantville -0.382590 0.469011 -0.816\nbeds:neighborhoodKensington 0.133474 0.664698 0.201\nbeds:neighborhoodLa Jolla 0.001347 0.085013 0.016\nbeds:neighborhoodLa Jolla Village -2.878676 1.020652 -2.820\nbeds:neighborhoodLinda Vista -0.142372 0.278211 -0.512\nbeds:neighborhoodLittle Italy -0.569868 0.099961 -5.701\nbeds:neighborhoodLoma Portal -0.255510 0.222956 -1.146\nbeds:neighborhoodMarina 0.024175 0.429466 0.056\nbeds:neighborhoodMidtown -0.346866 0.137915 -2.515\nbeds:neighborhoodMidtown District -0.464781 0.337775 -1.376\nbeds:neighborhoodMira Mesa 0.319934 0.426799 0.750\nbeds:neighborhoodMission Bay -0.108936 0.067105 -1.623\nbeds:neighborhoodMission Valley -0.502441 0.879795 -0.571\nbeds:neighborhoodMoreno Mission 0.492514 0.439355 1.121\nbeds:neighborhoodNormal Heights -0.532907 0.227211 -2.345\nbeds:neighborhoodNorth Clairemont 0.562363 0.704213 0.799\nbeds:neighborhoodNorth Hills -0.279430 0.123678 -2.259\nbeds:neighborhoodNorthwest 0.742017 0.474903 1.562\nbeds:neighborhoodOcean Beach -0.667651 0.137647 -4.850\nbeds:neighborhoodOld Town 0.459210 0.287008 1.600\nbeds:neighborhoodOtay Ranch 0.235723 0.983870 0.240\nbeds:neighborhoodPacific Beach -0.179242 0.087511 -2.048\nbeds:neighborhoodPark West -0.873297 0.225334 -3.876\nbeds:neighborhoodRancho Bernadino 0.378088 0.348640 1.084\nbeds:neighborhoodRancho Penasquitos 0.147457 0.344820 0.428\nbeds:neighborhoodRoseville -0.391529 0.328609 -1.191\nbeds:neighborhoodSan Carlos 0.115338 0.621666 0.186\nbeds:neighborhoodScripps Ranch -1.654484 0.338331 -4.890\nbeds:neighborhoodSerra Mesa -1.018812 0.705888 -1.443\nbeds:neighborhoodSouth Park 0.452815 0.406052 1.115\nbeds:neighborhoodUniversity City -0.345822 0.232779 -1.486\nbeds:neighborhoodWest University Heights 0.146128 0.364075 0.401\n Pr(>|t|) \naccommodates:neighborhoodBalboa Park 0.495668 \naccommodates:neighborhoodBay Ho 0.438397 \naccommodates:neighborhoodBay Park 0.126774 \naccommodates:neighborhoodCarmel Valley 0.488131 \naccommodates:neighborhoodCity Heights West 0.053861 . \naccommodates:neighborhoodClairemont Mesa 6.40e-05 ***\naccommodates:neighborhoodCollege Area 0.065740 . \naccommodates:neighborhoodCore 0.396446 \naccommodates:neighborhoodCortez Hill 1.61e-08 ***\naccommodates:neighborhoodDel Mar Heights 0.000108 ***\naccommodates:neighborhoodEast Village 0.005565 ** \naccommodates:neighborhoodGaslamp Quarter 0.022395 * \naccommodates:neighborhoodGrant Hill 1.45e-11 ***\naccommodates:neighborhoodGrantville 0.283202 \naccommodates:neighborhoodKensington 0.137520 \naccommodates:neighborhoodLa Jolla < 2e-16 ***\naccommodates:neighborhoodLa Jolla Village 0.170446 \naccommodates:neighborhoodLinda Vista 0.063414 . \naccommodates:neighborhoodLittle Italy 7.47e-07 ***\naccommodates:neighborhoodLoma Portal 0.000138 ***\naccommodates:neighborhoodMarina 0.012484 * \naccommodates:neighborhoodMidtown 2.40e-12 ***\naccommodates:neighborhoodMidtown District 0.024780 * \naccommodates:neighborhoodMira Mesa 0.953213 \naccommodates:neighborhoodMission Bay < 2e-16 ***\naccommodates:neighborhoodMission Valley 0.776000 \naccommodates:neighborhoodMoreno Mission 0.661505 \naccommodates:neighborhoodNormal Heights 0.037279 * \naccommodates:neighborhoodNorth Clairemont 0.429327 \naccommodates:neighborhoodNorth Hills 0.001602 ** \naccommodates:neighborhoodNorthwest 0.370103 \naccommodates:neighborhoodOcean Beach < 2e-16 ***\naccommodates:neighborhoodOld Town 0.267333 \naccommodates:neighborhoodOtay Ranch 0.281298 \naccommodates:neighborhoodPacific Beach 2.12e-09 ***\naccommodates:neighborhoodPark West 5.96e-09 ***\naccommodates:neighborhoodRancho Bernadino 0.643202 \naccommodates:neighborhoodRancho Penasquitos 0.593817 \naccommodates:neighborhoodRoseville 0.161071 \naccommodates:neighborhoodSan Carlos 0.548479 \naccommodates:neighborhoodScripps Ranch 0.006311 ** \naccommodates:neighborhoodSerra Mesa 0.079258 . \naccommodates:neighborhoodSouth Park 0.192775 \naccommodates:neighborhoodUniversity City 0.344762 \naccommodates:neighborhoodWest University Heights 0.369719 \nbathrooms:neighborhoodBalboa Park < 2e-16 ***\nbathrooms:neighborhoodBay Ho 4.60e-10 ***\nbathrooms:neighborhoodBay Park 1.11e-09 ***\nbathrooms:neighborhoodCarmel Valley 1.11e-07 ***\nbathrooms:neighborhoodCity Heights West 4.87e-06 ***\nbathrooms:neighborhoodClairemont Mesa < 2e-16 ***\nbathrooms:neighborhoodCollege Area 4.37e-16 ***\nbathrooms:neighborhoodCore 1.45e-13 ***\nbathrooms:neighborhoodCortez Hill 5.45e-08 ***\nbathrooms:neighborhoodDel Mar Heights 8.71e-05 ***\nbathrooms:neighborhoodEast Village < 2e-16 ***\nbathrooms:neighborhoodGaslamp Quarter 1.71e-09 ***\nbathrooms:neighborhoodGrant Hill 3.25e-11 ***\nbathrooms:neighborhoodGrantville 0.001704 ** \nbathrooms:neighborhoodKensington 0.055892 . \nbathrooms:neighborhoodLa Jolla < 2e-16 ***\nbathrooms:neighborhoodLa Jolla Village 0.409651 \nbathrooms:neighborhoodLinda Vista 2.10e-09 ***\nbathrooms:neighborhoodLittle Italy < 2e-16 ***\nbathrooms:neighborhoodLoma Portal < 2e-16 ***\nbathrooms:neighborhoodMarina 4.49e-07 ***\nbathrooms:neighborhoodMidtown 1.28e-15 ***\nbathrooms:neighborhoodMidtown District 1.31e-07 ***\nbathrooms:neighborhoodMira Mesa 2.55e-08 ***\nbathrooms:neighborhoodMission Bay < 2e-16 ***\nbathrooms:neighborhoodMission Valley 0.050935 . \nbathrooms:neighborhoodMoreno Mission 7.07e-09 ***\nbathrooms:neighborhoodNormal Heights 7.88e-14 ***\nbathrooms:neighborhoodNorth Clairemont 2.74e-05 ***\nbathrooms:neighborhoodNorth Hills < 2e-16 ***\nbathrooms:neighborhoodNorthwest 4.58e-07 ***\nbathrooms:neighborhoodOcean Beach 2.85e-16 ***\nbathrooms:neighborhoodOld Town 1.25e-13 ***\nbathrooms:neighborhoodOtay Ranch 0.012863 * \nbathrooms:neighborhoodPacific Beach < 2e-16 ***\nbathrooms:neighborhoodPark West < 2e-16 ***\nbathrooms:neighborhoodRancho Bernadino 0.122861 \nbathrooms:neighborhoodRancho Penasquitos 0.102129 \nbathrooms:neighborhoodRoseville 0.002070 ** \nbathrooms:neighborhoodSan Carlos 1.14e-06 ***\nbathrooms:neighborhoodScripps Ranch 1.68e-07 ***\nbathrooms:neighborhoodSerra Mesa 8.66e-07 ***\nbathrooms:neighborhoodSouth Park 3.00e-08 ***\nbathrooms:neighborhoodUniversity City < 2e-16 ***\nbathrooms:neighborhoodWest University Heights 2.18e-08 ***\nbedrooms:neighborhoodBalboa Park 0.013608 * \nbedrooms:neighborhoodBay Ho 0.185782 \nbedrooms:neighborhoodBay Park 0.013803 * \nbedrooms:neighborhoodCarmel Valley 0.277400 \nbedrooms:neighborhoodCity Heights West 0.681416 \nbedrooms:neighborhoodClairemont Mesa 0.099236 . \nbedrooms:neighborhoodCollege Area 0.455100 \nbedrooms:neighborhoodCore 0.047070 * \nbedrooms:neighborhoodCortez Hill 0.037033 * \nbedrooms:neighborhoodDel Mar Heights 0.847309 \nbedrooms:neighborhoodEast Village 0.383650 \nbedrooms:neighborhoodGaslamp Quarter 0.390058 \nbedrooms:neighborhoodGrant Hill 0.282895 \nbedrooms:neighborhoodGrantville 0.239317 \nbedrooms:neighborhoodKensington 0.798872 \nbedrooms:neighborhoodLa Jolla 0.255422 \nbedrooms:neighborhoodLa Jolla Village 0.022623 * \nbedrooms:neighborhoodLinda Vista 0.450143 \nbedrooms:neighborhoodLittle Italy 0.851191 \nbedrooms:neighborhoodLoma Portal 0.104857 \nbedrooms:neighborhoodMarina 0.104332 \nbedrooms:neighborhoodMidtown 3.37e-05 ***\nbedrooms:neighborhoodMidtown District 0.983369 \nbedrooms:neighborhoodMira Mesa 0.800169 \nbedrooms:neighborhoodMission Bay 0.006583 ** \nbedrooms:neighborhoodMission Valley 0.691053 \nbedrooms:neighborhoodMoreno Mission 0.326658 \nbedrooms:neighborhoodNormal Heights 0.744334 \nbedrooms:neighborhoodNorth Clairemont 0.685798 \nbedrooms:neighborhoodNorth Hills 0.033080 * \nbedrooms:neighborhoodNorthwest 0.634643 \nbedrooms:neighborhoodOcean Beach 0.854736 \nbedrooms:neighborhoodOld Town 0.394173 \nbedrooms:neighborhoodOtay Ranch 0.990680 \nbedrooms:neighborhoodPacific Beach 0.785097 \nbedrooms:neighborhoodPark West 0.092450 . \nbedrooms:neighborhoodRancho Bernadino 0.074358 . \nbedrooms:neighborhoodRancho Penasquitos 0.193994 \nbedrooms:neighborhoodRoseville 0.137390 \nbedrooms:neighborhoodSan Carlos 0.465713 \nbedrooms:neighborhoodScripps Ranch 0.000990 ***\nbedrooms:neighborhoodSerra Mesa 0.683614 \nbedrooms:neighborhoodSouth Park 0.144046 \nbedrooms:neighborhoodUniversity City 0.654804 \nbedrooms:neighborhoodWest University Heights 0.499025 \nbeds:neighborhoodBalboa Park 0.810374 \nbeds:neighborhoodBay Ho 0.416352 \nbeds:neighborhoodBay Park 0.489423 \nbeds:neighborhoodCarmel Valley 0.601836 \nbeds:neighborhoodCity Heights West 0.662195 \nbeds:neighborhoodClairemont Mesa 0.000332 ***\nbeds:neighborhoodCollege Area 0.057226 . \nbeds:neighborhoodCore 0.029663 * \nbeds:neighborhoodCortez Hill 2.18e-05 ***\nbeds:neighborhoodDel Mar Heights 0.005914 ** \nbeds:neighborhoodEast Village 0.007156 ** \nbeds:neighborhoodGaslamp Quarter 0.452696 \nbeds:neighborhoodGrant Hill 2.04e-12 ***\nbeds:neighborhoodGrantville 0.414683 \nbeds:neighborhoodKensington 0.840859 \nbeds:neighborhoodLa Jolla 0.987357 \nbeds:neighborhoodLa Jolla Village 0.004812 ** \nbeds:neighborhoodLinda Vista 0.608851 \nbeds:neighborhoodLittle Italy 1.25e-08 ***\nbeds:neighborhoodLoma Portal 0.251837 \nbeds:neighborhoodMarina 0.955112 \nbeds:neighborhoodMidtown 0.011927 * \nbeds:neighborhoodMidtown District 0.168872 \nbeds:neighborhoodMira Mesa 0.453518 \nbeds:neighborhoodMission Bay 0.104565 \nbeds:neighborhoodMission Valley 0.567962 \nbeds:neighborhoodMoreno Mission 0.262337 \nbeds:neighborhoodNormal Heights 0.019038 * \nbeds:neighborhoodNorth Clairemont 0.424572 \nbeds:neighborhoodNorth Hills 0.023899 * \nbeds:neighborhoodNorthwest 0.118233 \nbeds:neighborhoodOcean Beach 1.26e-06 ***\nbeds:neighborhoodOld Town 0.109654 \nbeds:neighborhoodOtay Ranch 0.810658 \nbeds:neighborhoodPacific Beach 0.040583 * \nbeds:neighborhoodPark West 0.000108 ***\nbeds:neighborhoodRancho Bernadino 0.278202 \nbeds:neighborhoodRancho Penasquitos 0.668932 \nbeds:neighborhoodRoseville 0.233515 \nbeds:neighborhoodSan Carlos 0.852819 \nbeds:neighborhoodScripps Ranch 1.03e-06 ***\nbeds:neighborhoodSerra Mesa 0.148987 \nbeds:neighborhoodSouth Park 0.264826 \nbeds:neighborhoodUniversity City 0.137433 \nbeds:neighborhoodWest University Heights 0.688164 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 1.81 on 5930 degrees of freedom\nMultiple R-squared: 0.8759, Adjusted R-squared: 0.8721 \nF-statistic: 232.4 on 180 and 5930 DF, p-value: < 2.2e-16\n\n\nThis allows us to get a separate constant term and estimate of the impact of each variable for every neighborhood" + "section": "\n6.5 Spatial heterogeneity", + "text": "6.5 Spatial heterogeneity\nSpatial heterogeneity (SH) arises when we cannot safely assume the process we are studying operates under the same “rules” throughout the geography of interest. In other words, we can observe SH when there are effects on the outcome variable that are intrinsically linked to specific locations. A good example of this is the case of seafront houses above: we are trying to model the price of a house and, the fact some houses are located under certain conditions (i.e. by the sea), makes their price behave differently. This somewhat abstract concept of SH can be made operational in a model in several ways. We will explore the following two: spatial fixed-effects (FE); and spatial regimes, which is a generalization of FE.\nSpatial FE\nLet us consider the house price example from the previous section to introduce a more general illustration that relates to the second motivation for spatial effects (“space as a proxy”). Given we are only including two explanatory variables in the model, it is likely we are missing some important factors that play a role at determining the price at which a house is sold. Some of them, however, are likely to vary systematically over space (e.g. different neighborhood characteristics). If that is the case, we can control for those unobserved factors by using traditional dummy variables but basing their creation on a spatial rule. For example, let us include a binary variable for every neighbourhood, as provided by AirBnB, indicating whether a given house is located within such area (1) or not (0). Neighbourhood membership is expressed on the neighborhood column:\n\ndb %>%\n ggplot(aes(color = neighborhood)) +\n geom_sf() + \n theme_void()\n\n\n\n\n\n\n\nMathematically, we are now fitting the following equation:\n\\[\n\\log(P_i) = \\alpha_r + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nwhere the main difference is that we are now allowing the constant term, \\(\\alpha\\), to vary by neighbourhood \\(r\\), \\(\\alpha_r\\).\nProgrammatically, we can fit this model with lm:\n\n# Include `-1` to eliminate the constant term and include a dummy for every area\nm2 <- lm(\n 'log_price ~ neighborhood + accommodates + bathrooms + bedrooms + beds - 1', \n db\n)\nsummary(m2)\n\n\nCall:\nlm(formula = \"log_price ~ neighborhood + accommodates + bathrooms + bedrooms + beds - 1\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.4549 -0.2920 -0.0203 0.2741 3.5323 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \nneighborhoodBalboa Park 3.994775 0.036539 109.33 <2e-16 ***\nneighborhoodBay Ho 3.780025 0.086081 43.91 <2e-16 ***\nneighborhoodBay Park 3.941847 0.055788 70.66 <2e-16 ***\nneighborhoodCarmel Valley 4.034052 0.062811 64.23 <2e-16 ***\nneighborhoodCity Heights West 3.698788 0.065502 56.47 <2e-16 ***\nneighborhoodClairemont Mesa 3.658339 0.051438 71.12 <2e-16 ***\nneighborhoodCollege Area 3.649859 0.064979 56.17 <2e-16 ***\nneighborhoodCore 4.433447 0.058864 75.32 <2e-16 ***\nneighborhoodCortez Hill 4.294790 0.057648 74.50 <2e-16 ***\nneighborhoodDel Mar Heights 4.300659 0.060912 70.61 <2e-16 ***\nneighborhoodEast Village 4.241146 0.032019 132.46 <2e-16 ***\nneighborhoodGaslamp Quarter 4.473863 0.052493 85.23 <2e-16 ***\nneighborhoodGrant Hill 4.001481 0.058825 68.02 <2e-16 ***\nneighborhoodGrantville 3.664989 0.080168 45.72 <2e-16 ***\nneighborhoodKensington 4.073520 0.087322 46.65 <2e-16 ***\nneighborhoodLa Jolla 4.400145 0.026772 164.36 <2e-16 ***\nneighborhoodLa Jolla Village 4.066151 0.087263 46.60 <2e-16 ***\nneighborhoodLinda Vista 3.817940 0.063128 60.48 <2e-16 ***\nneighborhoodLittle Italy 4.390651 0.052433 83.74 <2e-16 ***\nneighborhoodLoma Portal 4.034473 0.036173 111.53 <2e-16 ***\nneighborhoodMarina 4.046133 0.052178 77.55 <2e-16 ***\nneighborhoodMidtown 4.032038 0.030280 133.16 <2e-16 ***\nneighborhoodMidtown District 4.356943 0.071756 60.72 <2e-16 ***\nneighborhoodMira Mesa 3.570523 0.061543 58.02 <2e-16 ***\nneighborhoodMission Bay 4.251309 0.023318 182.32 <2e-16 ***\nneighborhoodMission Valley 4.012410 0.083766 47.90 <2e-16 ***\nneighborhoodMoreno Mission 4.028288 0.063342 63.59 <2e-16 ***\nneighborhoodNormal Heights 3.791895 0.054730 69.28 <2e-16 ***\nneighborhoodNorth Clairemont 3.498107 0.076432 45.77 <2e-16 ***\nneighborhoodNorth Hills 3.959403 0.026823 147.61 <2e-16 ***\nneighborhoodNorthwest 3.810201 0.078158 48.75 <2e-16 ***\nneighborhoodOcean Beach 4.152695 0.032352 128.36 <2e-16 ***\nneighborhoodOld Town 4.127737 0.046523 88.72 <2e-16 ***\nneighborhoodOtay Ranch 3.722902 0.091633 40.63 <2e-16 ***\nneighborhoodPacific Beach 4.116749 0.022711 181.27 <2e-16 ***\nneighborhoodPark West 4.216829 0.050370 83.72 <2e-16 ***\nneighborhoodRancho Bernadino 3.873962 0.080780 47.96 <2e-16 ***\nneighborhoodRancho Penasquitos 3.772037 0.068808 54.82 <2e-16 ***\nneighborhoodRoseville 4.070468 0.065299 62.34 <2e-16 ***\nneighborhoodSan Carlos 3.935042 0.093205 42.22 <2e-16 ***\nneighborhoodScripps Ranch 3.641239 0.085190 42.74 <2e-16 ***\nneighborhoodSerra Mesa 3.912127 0.066630 58.71 <2e-16 ***\nneighborhoodSouth Park 3.987019 0.060141 66.30 <2e-16 ***\nneighborhoodUniversity City 3.772504 0.039638 95.17 <2e-16 ***\nneighborhoodWest University Heights 4.043161 0.048238 83.82 <2e-16 ***\naccommodates 0.150283 0.005086 29.55 <2e-16 ***\nbathrooms 0.132287 0.011886 11.13 <2e-16 ***\nbedrooms 0.147631 0.011960 12.34 <2e-16 ***\nbeds -0.074622 0.007405 -10.08 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.4971 on 6061 degrees of freedom\nMultiple R-squared: 0.9904, Adjusted R-squared: 0.9904 \nF-statistic: 1.28e+04 on 49 and 6061 DF, p-value: < 2.2e-16\n\n\nEconometrically speaking, what the postcode FE we have introduced imply is that, instead of comparing all house prices across San Diego as equal, we only derive variation from within each postcode. In our particular case, estimating spatial FE in our particular example also gives you an indirect measure of area desirability: since they are simple dummies in a regression explaining the price of a house, their estimate tells us about how much people are willing to pay to live in a given area. We can visualise this “geography of desirability” by plotting the estimates of each fixed effect on a map:\n\n# Extract neighborhood names from coefficients\nnei.names <- m2$coefficients %>%\n as.data.frame() %>%\n row.names() %>%\n str_replace(\"neighborhood\", \"\")\n# Set up as Data Frame\nnei.fes <- data.frame(\n coef = m2$coefficients,\n nei = nei.names,\n row.names = nei.names\n) %>%\n right_join(\n db, by = c(\"nei\" = \"neighborhood\")\n)\n# Plot\nnei.fes %>%\n st_as_sf() %>%\n ggplot(aes(color = coef)) +\n geom_sf() +\n scale_color_viridis_c() +\n theme_void()\n\n\n\n\n\n\n\nWe can see how neighborhoods in the left (west) tend to have higher prices. What we can’t see, but it is represented there if you are familiar with the geography of San Diego, is that the city is bounded by the Pacific ocean on the left, suggesting neighbourhoods by the beach tend to be more expensive.\nRemember that the interpretation of a \\(\\beta_k\\) coefficient is the effect of variable \\(k\\), given all the other explanatory variables included remain constant. By including a single variable for each area, we are effectively forcing the model to compare as equal only house prices that share the same value for each variable; in other words, only houses located within the same area. Introducing FE affords you a higher degree of isolation of the effects of the variables you introduce in your model because you can control for unobserved effects that align spatially with the distribution of the FE you introduce (by neighbourhood, in our case).\nSpatial regimes\nAt the core of estimating spatial FEs is the idea that, instead of assuming the dependent variable behaves uniformly over space, there are systematic effects following a geographical pattern that affect its behaviour. In other words, spatial FEs introduce econometrically the notion of spatial heterogeneity. They do this in the simplest possible form: by allowing the constant term to vary geographically. The other elements of the regression are left untouched and hence apply uniformly across space. The idea of spatial regimes (SRs) is to generalize the spatial FE approach to allow not only the constant term to vary but also any other explanatory variable. This implies that the equation we will be estimating is: \\[\\log(P_i) = \\alpha_r + \\beta_{1r} Acc_i + \\beta_{2r} Bath_i + \\beta_{3r} Bedr_i + \\beta_{4r} Beds_i + \\epsilon_i\\]\nwhere we are not only allowing the constant term to vary by region (\\(\\alpha_r\\)), but also every other parameter (\\(\\beta_{kr}\\)).\nAlso, given we are going to allow every coefficient to vary by regime, we will need to explicitly set a constant term that we can allow to vary:\n\ndb$one <- 1\n\nThen, the estimation leverages the capabilities in model description of R formulas:\n\n# `:` notation implies interaction variables\nm3 <- lm(\n 'log_price ~ (one + accommodates + bathrooms + bedrooms + beds):(neighborhood)', \n db\n)\nsummary(m3)\n\n\nCall:\nlm(formula = \"log_price ~ (one + accommodates + bathrooms + bedrooms + beds):(neighborhood)\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.5528 -0.2921 -0.0163 0.2586 3.1874 \n\nCoefficients: (1 not defined because of singularities)\n Estimate Std. Error t value\n(Intercept) 4.012160 0.122261 32.816\none:neighborhoodBalboa Park 0.128350 0.145826 0.880\none:neighborhoodBay Ho -0.202575 0.254393 -0.796\none:neighborhoodBay Park -0.272843 0.174512 -1.563\none:neighborhoodCarmel Valley -0.063356 0.164404 -0.385\none:neighborhoodCity Heights West -0.096400 0.205758 -0.469\none:neighborhoodClairemont Mesa -0.639595 0.183891 -3.478\none:neighborhoodCollege Area -0.185039 0.207335 -0.892\none:neighborhoodCore 0.416563 0.223962 1.860\none:neighborhoodCortez Hill 0.309752 0.193169 1.604\none:neighborhoodDel Mar Heights 0.259677 0.182046 1.426\none:neighborhoodEast Village 0.205331 0.147158 1.395\none:neighborhoodGaslamp Quarter -1.156797 0.282853 -4.090\none:neighborhoodGrant Hill -0.077324 0.200580 -0.386\none:neighborhoodGrantville -0.355260 0.278718 -1.275\none:neighborhoodKensington -0.252743 0.248147 -1.019\none:neighborhoodLa Jolla 0.380059 0.128014 2.969\none:neighborhoodLa Jolla Village 0.027119 0.291318 0.093\none:neighborhoodLinda Vista -0.448116 0.202151 -2.217\none:neighborhoodLittle Italy 0.384100 0.188630 2.036\none:neighborhoodLoma Portal 0.014552 0.148157 0.098\none:neighborhoodMarina -0.549055 0.198350 -2.768\none:neighborhoodMidtown 0.071392 0.134620 0.530\none:neighborhoodMidtown District -0.180003 0.219627 -0.820\none:neighborhoodMira Mesa -0.596573 0.205262 -2.906\none:neighborhoodMission Bay 0.399054 0.128693 3.101\none:neighborhoodMission Valley -0.249279 0.288450 -0.864\none:neighborhoodMoreno Mission 0.268901 0.222999 1.206\none:neighborhoodNormal Heights -0.253917 0.210697 -1.205\none:neighborhoodNorth Clairemont -0.404436 0.221559 -1.825\none:neighborhoodNorth Hills -0.103473 0.136994 -0.755\none:neighborhoodNorthwest 0.198074 0.284402 0.696\none:neighborhoodOcean Beach 0.192554 0.136493 1.411\none:neighborhoodOld Town -0.062746 0.157153 -0.399\none:neighborhoodOtay Ranch -0.358005 0.232826 -1.538\none:neighborhoodPacific Beach 0.113854 0.129413 0.880\none:neighborhoodPark West 0.154992 0.170398 0.910\none:neighborhoodRancho Bernadino -0.019347 0.194468 -0.099\none:neighborhoodRancho Penasquitos -0.401257 0.163315 -2.457\none:neighborhoodRoseville 0.120010 0.182676 0.657\none:neighborhoodSan Carlos -0.589123 0.284463 -2.071\none:neighborhoodScripps Ranch -0.026842 0.215965 -0.124\none:neighborhoodSerra Mesa -0.283949 0.207741 -1.367\none:neighborhoodSouth Park -0.181786 0.226828 -0.801\none:neighborhoodUniversity City -0.298526 0.161655 -1.847\none:neighborhoodWest University Heights NA NA NA\naccommodates:neighborhoodBalboa Park 0.097052 0.024632 3.940\naccommodates:neighborhoodBay Ho -0.017961 0.089598 -0.200\naccommodates:neighborhoodBay Park 0.155715 0.063773 2.442\naccommodates:neighborhoodCarmel Valley 0.151990 0.049440 3.074\naccommodates:neighborhoodCity Heights West 0.060303 0.063416 0.951\naccommodates:neighborhoodClairemont Mesa 0.260414 0.050425 5.164\naccommodates:neighborhoodCollege Area 0.084693 0.053125 1.594\naccommodates:neighborhoodCore 0.106661 0.039204 2.721\naccommodates:neighborhoodCortez Hill 0.217358 0.037615 5.779\naccommodates:neighborhoodDel Mar Heights 0.075776 0.061335 1.235\naccommodates:neighborhoodEast Village 0.162155 0.020465 7.924\naccommodates:neighborhoodGaslamp Quarter 0.307007 0.053766 5.710\naccommodates:neighborhoodGrant Hill 0.278114 0.056238 4.945\naccommodates:neighborhoodGrantville 0.124025 0.075028 1.653\naccommodates:neighborhoodKensington 0.063298 0.123887 0.511\naccommodates:neighborhoodLa Jolla 0.119142 0.015162 7.858\naccommodates:neighborhoodLa Jolla Village 0.207085 0.111627 1.855\naccommodates:neighborhoodLinda Vista 0.172101 0.076208 2.258\naccommodates:neighborhoodLittle Italy 0.230108 0.034432 6.683\naccommodates:neighborhoodLoma Portal 0.090991 0.034762 2.618\naccommodates:neighborhoodMarina 0.552162 0.045914 12.026\naccommodates:neighborhoodMidtown 0.227617 0.023854 9.542\naccommodates:neighborhoodMidtown District 0.179989 0.052007 3.461\naccommodates:neighborhoodMira Mesa 0.157603 0.082351 1.914\naccommodates:neighborhoodMission Bay 0.085754 0.013455 6.373\naccommodates:neighborhoodMission Valley 0.099273 0.134176 0.740\naccommodates:neighborhoodMoreno Mission 0.145824 0.060631 2.405\naccommodates:neighborhoodNormal Heights 0.237177 0.053060 4.470\naccommodates:neighborhoodNorth Clairemont 0.188941 0.084060 2.248\naccommodates:neighborhoodNorth Hills 0.143390 0.022069 6.497\naccommodates:neighborhoodNorthwest 0.022166 0.069235 0.320\naccommodates:neighborhoodOcean Beach 0.113995 0.022845 4.990\naccommodates:neighborhoodOld Town 0.239088 0.042208 5.665\naccommodates:neighborhoodOtay Ranch -0.021530 0.083481 -0.258\naccommodates:neighborhoodPacific Beach 0.153379 0.015339 9.999\naccommodates:neighborhoodPark West 0.224593 0.045581 4.927\naccommodates:neighborhoodRancho Bernadino 0.004730 0.067968 0.070\naccommodates:neighborhoodRancho Penasquitos 0.070627 0.060353 1.170\naccommodates:neighborhoodRoseville 0.112686 0.060090 1.875\naccommodates:neighborhoodSan Carlos -0.140714 0.087777 -1.603\naccommodates:neighborhoodScripps Ranch 0.087078 0.035562 2.449\naccommodates:neighborhoodSerra Mesa 0.190589 0.075803 2.514\naccommodates:neighborhoodSouth Park 0.206833 0.068093 3.038\naccommodates:neighborhoodUniversity City 0.067637 0.030097 2.247\naccommodates:neighborhoodWest University Heights 0.124850 0.056034 2.228\nbathrooms:neighborhoodBalboa Park 0.014875 0.073585 0.202\nbathrooms:neighborhoodBay Ho 0.305028 0.225044 1.355\nbathrooms:neighborhoodBay Park 0.065043 0.120545 0.540\nbathrooms:neighborhoodCarmel Valley 0.205847 0.065237 3.155\nbathrooms:neighborhoodCity Heights West -0.129465 0.183389 -0.706\nbathrooms:neighborhoodClairemont Mesa 0.418033 0.176111 2.374\nbathrooms:neighborhoodCollege Area -0.012095 0.183917 -0.066\nbathrooms:neighborhoodCore 0.464974 0.186634 2.491\nbathrooms:neighborhoodCortez Hill -0.206708 0.101521 -2.036\nbathrooms:neighborhoodDel Mar Heights 0.319046 0.096172 3.317\nbathrooms:neighborhoodEast Village 0.312707 0.067204 4.653\nbathrooms:neighborhoodGaslamp Quarter 1.459299 0.207669 7.027\nbathrooms:neighborhoodGrant Hill 0.040405 0.155867 0.259\nbathrooms:neighborhoodGrantville 0.028466 0.234973 0.121\nbathrooms:neighborhoodKensington 0.146585 0.188987 0.776\nbathrooms:neighborhoodLa Jolla 0.242760 0.026778 9.066\nbathrooms:neighborhoodLa Jolla Village -0.296748 0.326257 -0.910\nbathrooms:neighborhoodLinda Vista 0.170333 0.143441 1.187\nbathrooms:neighborhoodLittle Italy 0.035532 0.111044 0.320\nbathrooms:neighborhoodLoma Portal 0.188467 0.082511 2.284\nbathrooms:neighborhoodMarina 0.316509 0.219948 1.439\nbathrooms:neighborhoodMidtown -0.037771 0.032333 -1.168\nbathrooms:neighborhoodMidtown District 0.623515 0.197674 3.154\nbathrooms:neighborhoodMira Mesa 0.215939 0.185982 1.161\nbathrooms:neighborhoodMission Bay 0.176976 0.035418 4.997\nbathrooms:neighborhoodMission Valley -0.009144 0.405637 -0.023\nbathrooms:neighborhoodMoreno Mission -0.208248 0.210241 -0.991\nbathrooms:neighborhoodNormal Heights 0.017303 0.201580 0.086\nbathrooms:neighborhoodNorth Clairemont -0.102553 0.212254 -0.483\nbathrooms:neighborhoodNorth Hills 0.098449 0.064065 1.537\nbathrooms:neighborhoodNorthwest -0.503592 0.255300 -1.973\nbathrooms:neighborhoodOcean Beach 0.082659 0.059578 1.387\nbathrooms:neighborhoodOld Town 0.087046 0.096513 0.902\nbathrooms:neighborhoodOtay Ranch -0.219480 0.341525 -0.643\nbathrooms:neighborhoodPacific Beach 0.116105 0.041006 2.831\nbathrooms:neighborhoodPark West 0.198550 0.107749 1.843\nbathrooms:neighborhoodRancho Bernadino 0.365674 0.147799 2.474\nbathrooms:neighborhoodRancho Penasquitos 0.320715 0.110007 2.915\nbathrooms:neighborhoodRoseville -0.053420 0.102487 -0.521\nbathrooms:neighborhoodSan Carlos 0.267797 0.214540 1.248\nbathrooms:neighborhoodScripps Ranch -0.132692 0.169684 -0.782\nbathrooms:neighborhoodSerra Mesa 0.381620 0.197305 1.934\nbathrooms:neighborhoodSouth Park 0.087490 0.196486 0.445\nbathrooms:neighborhoodUniversity City 0.138271 0.125198 1.104\nbathrooms:neighborhoodWest University Heights 0.042035 0.122097 0.344\nbedrooms:neighborhoodBalboa Park 0.183347 0.065309 2.807\nbedrooms:neighborhoodBay Ho 0.241200 0.170471 1.415\nbedrooms:neighborhoodBay Park 0.214111 0.117198 1.827\nbedrooms:neighborhoodCarmel Valley -0.073928 0.127976 -0.578\nbedrooms:neighborhoodCity Heights West 0.272272 0.176859 1.539\nbedrooms:neighborhoodClairemont Mesa -0.046650 0.122263 -0.382\nbedrooms:neighborhoodCollege Area -0.055707 0.108958 -0.511\nbedrooms:neighborhoodCore -0.069929 0.108896 -0.642\nbedrooms:neighborhoodCortez Hill 0.313288 0.101770 3.078\nbedrooms:neighborhoodDel Mar Heights 0.111554 0.097639 1.143\nbedrooms:neighborhoodEast Village 0.088071 0.056654 1.555\nbedrooms:neighborhoodGaslamp Quarter -0.297570 0.090336 -3.294\nbedrooms:neighborhoodGrant Hill 0.091300 0.114501 0.797\nbedrooms:neighborhoodGrantville 0.221461 0.208950 1.060\nbedrooms:neighborhoodKensington 0.450822 0.269641 1.672\nbedrooms:neighborhoodLa Jolla 0.166725 0.035423 4.707\nbedrooms:neighborhoodLa Jolla Village 0.845318 0.545842 1.549\nbedrooms:neighborhoodLinda Vista 0.096413 0.171751 0.561\nbedrooms:neighborhoodLittle Italy 0.126794 0.080939 1.567\nbedrooms:neighborhoodLoma Portal 0.172480 0.067268 2.564\nbedrooms:neighborhoodMarina -0.323085 0.137046 -2.357\nbedrooms:neighborhoodMidtown 0.172711 0.044927 3.844\nbedrooms:neighborhoodMidtown District 0.088181 0.134514 0.656\nbedrooms:neighborhoodMira Mesa -0.029149 0.206406 -0.141\nbedrooms:neighborhoodMission Bay 0.197484 0.032472 6.082\nbedrooms:neighborhoodMission Valley 0.393256 0.342308 1.149\nbedrooms:neighborhoodMoreno Mission -0.081003 0.158933 -0.510\nbedrooms:neighborhoodNormal Heights 0.199088 0.104514 1.905\nbedrooms:neighborhoodNorth Clairemont 0.176509 0.183702 0.961\nbedrooms:neighborhoodNorth Hills 0.283826 0.047159 6.018\nbedrooms:neighborhoodNorthwest 0.411641 0.160558 2.564\nbedrooms:neighborhoodOcean Beach 0.220345 0.055039 4.003\nbedrooms:neighborhoodOld Town 0.209031 0.099966 2.091\nbedrooms:neighborhoodOtay Ranch 0.772212 0.354230 2.180\nbedrooms:neighborhoodPacific Beach 0.077503 0.036735 2.110\nbedrooms:neighborhoodPark West -0.127891 0.110497 -1.157\nbedrooms:neighborhoodRancho Bernadino -0.209594 0.160210 -1.308\nbedrooms:neighborhoodRancho Penasquitos 0.107599 0.138183 0.779\nbedrooms:neighborhoodRoseville 0.395961 0.157406 2.516\nbedrooms:neighborhoodSan Carlos 0.239713 0.150420 1.594\nbedrooms:neighborhoodScripps Ranch -0.045580 0.102624 -0.444\nbedrooms:neighborhoodSerra Mesa 0.202756 0.163958 1.237\nbedrooms:neighborhoodSouth Park 0.080548 0.112861 0.714\nbedrooms:neighborhoodUniversity City 0.124090 0.120386 1.031\nbedrooms:neighborhoodWest University Heights 0.011105 0.095137 0.117\nbeds:neighborhoodBalboa Park 0.013322 0.045740 0.291\nbeds:neighborhoodBay Ho 0.037070 0.133299 0.278\nbeds:neighborhoodBay Park 0.011091 0.108863 0.102\nbeds:neighborhoodCarmel Valley 0.086393 0.076151 1.135\nbeds:neighborhoodCity Heights West 0.076520 0.132072 0.579\nbeds:neighborhoodClairemont Mesa -0.163512 0.090244 -1.812\nbeds:neighborhoodCollege Area 0.228165 0.084147 2.712\nbeds:neighborhoodCore -0.080211 0.078673 -1.020\nbeds:neighborhoodCortez Hill -0.039031 0.042752 -0.913\nbeds:neighborhoodDel Mar Heights -0.021375 0.071330 -0.300\nbeds:neighborhoodEast Village -0.174883 0.040811 -4.285\nbeds:neighborhoodGaslamp Quarter -0.064063 0.088752 -0.722\nbeds:neighborhoodGrant Hill -0.178920 0.067398 -2.655\nbeds:neighborhoodGrantville -0.003795 0.126551 -0.030\nbeds:neighborhoodKensington -0.005431 0.175724 -0.031\nbeds:neighborhoodLa Jolla -0.104906 0.022470 -4.669\nbeds:neighborhoodLa Jolla Village -0.385895 0.315094 -1.225\nbeds:neighborhoodLinda Vista 0.032215 0.073896 0.436\nbeds:neighborhoodLittle Italy -0.145613 0.029817 -4.884\nbeds:neighborhoodLoma Portal -0.013033 0.059097 -0.221\nbeds:neighborhoodMarina -0.210771 0.113913 -1.850\nbeds:neighborhoodMidtown -0.143254 0.036531 -3.921\nbeds:neighborhoodMidtown District -0.132655 0.090595 -1.464\nbeds:neighborhoodMira Mesa 0.097629 0.113225 0.862\nbeds:neighborhoodMission Bay -0.061315 0.017727 -3.459\nbeds:neighborhoodMission Valley 0.073187 0.235760 0.310\nbeds:neighborhoodMoreno Mission 0.195918 0.116748 1.678\nbeds:neighborhoodNormal Heights -0.185827 0.062062 -2.994\nbeds:neighborhoodNorth Clairemont -0.060468 0.188694 -0.320\nbeds:neighborhoodNorth Hills -0.113150 0.032768 -3.453\nbeds:neighborhoodNorthwest 0.116746 0.131088 0.891\nbeds:neighborhoodOcean Beach -0.048848 0.037433 -1.305\nbeds:neighborhoodOld Town -0.158066 0.077352 -2.043\nbeds:neighborhoodOtay Ranch 0.071485 0.259986 0.275\nbeds:neighborhoodPacific Beach -0.025064 0.023165 -1.082\nbeds:neighborhoodPark West -0.049627 0.063967 -0.776\nbeds:neighborhoodRancho Bernadino 0.270293 0.092164 2.933\nbeds:neighborhoodRancho Penasquitos 0.011344 0.091156 0.124\nbeds:neighborhoodRoseville -0.085205 0.087365 -0.975\nbeds:neighborhoodSan Carlos 0.550169 0.167389 3.287\nbeds:neighborhoodScripps Ranch 0.219496 0.122439 1.793\nbeds:neighborhoodSerra Mesa -0.293629 0.189261 -1.551\nbeds:neighborhoodSouth Park -0.020083 0.109799 -0.183\nbeds:neighborhoodUniversity City 0.124224 0.062916 1.974\nbeds:neighborhoodWest University Heights 0.143248 0.096150 1.490\n Pr(>|t|) \n(Intercept) < 2e-16 ***\none:neighborhoodBalboa Park 0.378810 \none:neighborhoodBay Ho 0.425887 \none:neighborhoodBay Park 0.117997 \none:neighborhoodCarmel Valley 0.699978 \none:neighborhoodCity Heights West 0.639437 \none:neighborhoodClairemont Mesa 0.000509 ***\none:neighborhoodCollege Area 0.372180 \none:neighborhoodCore 0.062939 . \none:neighborhoodCortez Hill 0.108871 \none:neighborhoodDel Mar Heights 0.153795 \none:neighborhoodEast Village 0.162975 \none:neighborhoodGaslamp Quarter 4.38e-05 ***\none:neighborhoodGrant Hill 0.699880 \none:neighborhoodGrantville 0.202493 \none:neighborhoodKensington 0.308471 \none:neighborhoodLa Jolla 0.003001 ** \none:neighborhoodLa Jolla Village 0.925835 \none:neighborhoodLinda Vista 0.026679 * \none:neighborhoodLittle Italy 0.041769 * \none:neighborhoodLoma Portal 0.921764 \none:neighborhoodMarina 0.005656 ** \none:neighborhoodMidtown 0.595908 \none:neighborhoodMidtown District 0.412485 \none:neighborhoodMira Mesa 0.003670 ** \none:neighborhoodMission Bay 0.001939 ** \none:neighborhoodMission Valley 0.387513 \none:neighborhoodMoreno Mission 0.227928 \none:neighborhoodNormal Heights 0.228202 \none:neighborhoodNorth Clairemont 0.067990 . \none:neighborhoodNorth Hills 0.450091 \none:neighborhoodNorthwest 0.486169 \none:neighborhoodOcean Beach 0.158381 \none:neighborhoodOld Town 0.689710 \none:neighborhoodOtay Ranch 0.124188 \none:neighborhoodPacific Beach 0.379016 \none:neighborhoodPark West 0.363079 \none:neighborhoodRancho Bernadino 0.920756 \none:neighborhoodRancho Penasquitos 0.014041 * \none:neighborhoodRoseville 0.511237 \none:neighborhoodSan Carlos 0.038402 * \none:neighborhoodScripps Ranch 0.901090 \none:neighborhoodSerra Mesa 0.171727 \none:neighborhoodSouth Park 0.422917 \none:neighborhoodUniversity City 0.064843 . \none:neighborhoodWest University Heights NA \naccommodates:neighborhoodBalboa Park 8.24e-05 ***\naccommodates:neighborhoodBay Ho 0.841129 \naccommodates:neighborhoodBay Park 0.014647 * \naccommodates:neighborhoodCarmel Valley 0.002120 ** \naccommodates:neighborhoodCity Heights West 0.341685 \naccommodates:neighborhoodClairemont Mesa 2.49e-07 ***\naccommodates:neighborhoodCollege Area 0.110943 \naccommodates:neighborhoodCore 0.006535 ** \naccommodates:neighborhoodCortez Hill 7.92e-09 ***\naccommodates:neighborhoodDel Mar Heights 0.216716 \naccommodates:neighborhoodEast Village 2.74e-15 ***\naccommodates:neighborhoodGaslamp Quarter 1.18e-08 ***\naccommodates:neighborhoodGrant Hill 7.81e-07 ***\naccommodates:neighborhoodGrantville 0.098376 . \naccommodates:neighborhoodKensington 0.609420 \naccommodates:neighborhoodLa Jolla 4.61e-15 ***\naccommodates:neighborhoodLa Jolla Village 0.063624 . \naccommodates:neighborhoodLinda Vista 0.023963 * \naccommodates:neighborhoodLittle Italy 2.56e-11 ***\naccommodates:neighborhoodLoma Portal 0.008879 ** \naccommodates:neighborhoodMarina < 2e-16 ***\naccommodates:neighborhoodMidtown < 2e-16 ***\naccommodates:neighborhoodMidtown District 0.000542 ***\naccommodates:neighborhoodMira Mesa 0.055697 . \naccommodates:neighborhoodMission Bay 1.99e-10 ***\naccommodates:neighborhoodMission Valley 0.459407 \naccommodates:neighborhoodMoreno Mission 0.016199 * \naccommodates:neighborhoodNormal Heights 7.97e-06 ***\naccommodates:neighborhoodNorth Clairemont 0.024632 * \naccommodates:neighborhoodNorth Hills 8.84e-11 ***\naccommodates:neighborhoodNorthwest 0.748867 \naccommodates:neighborhoodOcean Beach 6.21e-07 ***\naccommodates:neighborhoodOld Town 1.54e-08 ***\naccommodates:neighborhoodOtay Ranch 0.796490 \naccommodates:neighborhoodPacific Beach < 2e-16 ***\naccommodates:neighborhoodPark West 8.56e-07 ***\naccommodates:neighborhoodRancho Bernadino 0.944521 \naccommodates:neighborhoodRancho Penasquitos 0.241960 \naccommodates:neighborhoodRoseville 0.060803 . \naccommodates:neighborhoodSan Carlos 0.108968 \naccommodates:neighborhoodScripps Ranch 0.014369 * \naccommodates:neighborhoodSerra Mesa 0.011955 * \naccommodates:neighborhoodSouth Park 0.002396 ** \naccommodates:neighborhoodUniversity City 0.024659 * \naccommodates:neighborhoodWest University Heights 0.025910 * \nbathrooms:neighborhoodBalboa Park 0.839806 \nbathrooms:neighborhoodBay Ho 0.175338 \nbathrooms:neighborhoodBay Park 0.589509 \nbathrooms:neighborhoodCarmel Valley 0.001611 ** \nbathrooms:neighborhoodCity Heights West 0.480243 \nbathrooms:neighborhoodClairemont Mesa 0.017643 * \nbathrooms:neighborhoodCollege Area 0.947567 \nbathrooms:neighborhoodCore 0.012753 * \nbathrooms:neighborhoodCortez Hill 0.041784 * \nbathrooms:neighborhoodDel Mar Heights 0.000914 ***\nbathrooms:neighborhoodEast Village 3.34e-06 ***\nbathrooms:neighborhoodGaslamp Quarter 2.35e-12 ***\nbathrooms:neighborhoodGrant Hill 0.795468 \nbathrooms:neighborhoodGrantville 0.903578 \nbathrooms:neighborhoodKensington 0.437993 \nbathrooms:neighborhoodLa Jolla < 2e-16 ***\nbathrooms:neighborhoodLa Jolla Village 0.363095 \nbathrooms:neighborhoodLinda Vista 0.235088 \nbathrooms:neighborhoodLittle Italy 0.748992 \nbathrooms:neighborhoodLoma Portal 0.022399 * \nbathrooms:neighborhoodMarina 0.150200 \nbathrooms:neighborhoodMidtown 0.242779 \nbathrooms:neighborhoodMidtown District 0.001617 ** \nbathrooms:neighborhoodMira Mesa 0.245659 \nbathrooms:neighborhoodMission Bay 6.00e-07 ***\nbathrooms:neighborhoodMission Valley 0.982016 \nbathrooms:neighborhoodMoreno Mission 0.321960 \nbathrooms:neighborhoodNormal Heights 0.931598 \nbathrooms:neighborhoodNorth Clairemont 0.628997 \nbathrooms:neighborhoodNorth Hills 0.124417 \nbathrooms:neighborhoodNorthwest 0.048594 * \nbathrooms:neighborhoodOcean Beach 0.165371 \nbathrooms:neighborhoodOld Town 0.367140 \nbathrooms:neighborhoodOtay Ranch 0.520477 \nbathrooms:neighborhoodPacific Beach 0.004650 ** \nbathrooms:neighborhoodPark West 0.065420 . \nbathrooms:neighborhoodRancho Bernadino 0.013384 * \nbathrooms:neighborhoodRancho Penasquitos 0.003566 ** \nbathrooms:neighborhoodRoseville 0.602221 \nbathrooms:neighborhoodSan Carlos 0.211993 \nbathrooms:neighborhoodScripps Ranch 0.434248 \nbathrooms:neighborhoodSerra Mesa 0.053141 . \nbathrooms:neighborhoodSouth Park 0.656138 \nbathrooms:neighborhoodUniversity City 0.269455 \nbathrooms:neighborhoodWest University Heights 0.730652 \nbedrooms:neighborhoodBalboa Park 0.005011 ** \nbedrooms:neighborhoodBay Ho 0.157151 \nbedrooms:neighborhoodBay Park 0.067764 . \nbedrooms:neighborhoodCarmel Valley 0.563507 \nbedrooms:neighborhoodCity Heights West 0.123740 \nbedrooms:neighborhoodClairemont Mesa 0.702806 \nbedrooms:neighborhoodCollege Area 0.609182 \nbedrooms:neighborhoodCore 0.520791 \nbedrooms:neighborhoodCortez Hill 0.002091 ** \nbedrooms:neighborhoodDel Mar Heights 0.253288 \nbedrooms:neighborhoodEast Village 0.120105 \nbedrooms:neighborhoodGaslamp Quarter 0.000993 ***\nbedrooms:neighborhoodGrant Hill 0.425266 \nbedrooms:neighborhoodGrantville 0.289243 \nbedrooms:neighborhoodKensington 0.094590 . \nbedrooms:neighborhoodLa Jolla 2.58e-06 ***\nbedrooms:neighborhoodLa Jolla Village 0.121520 \nbedrooms:neighborhoodLinda Vista 0.574579 \nbedrooms:neighborhoodLittle Italy 0.117274 \nbedrooms:neighborhoodLoma Portal 0.010370 * \nbedrooms:neighborhoodMarina 0.018431 * \nbedrooms:neighborhoodMidtown 0.000122 ***\nbedrooms:neighborhoodMidtown District 0.512137 \nbedrooms:neighborhoodMira Mesa 0.887701 \nbedrooms:neighborhoodMission Bay 1.26e-09 ***\nbedrooms:neighborhoodMission Valley 0.250670 \nbedrooms:neighborhoodMoreno Mission 0.610302 \nbedrooms:neighborhoodNormal Heights 0.056842 . \nbedrooms:neighborhoodNorth Clairemont 0.336669 \nbedrooms:neighborhoodNorth Hills 1.87e-09 ***\nbedrooms:neighborhoodNorthwest 0.010377 * \nbedrooms:neighborhoodOcean Beach 6.32e-05 ***\nbedrooms:neighborhoodOld Town 0.036570 * \nbedrooms:neighborhoodOtay Ranch 0.029299 * \nbedrooms:neighborhoodPacific Beach 0.034919 * \nbedrooms:neighborhoodPark West 0.247150 \nbedrooms:neighborhoodRancho Bernadino 0.190841 \nbedrooms:neighborhoodRancho Penasquitos 0.436205 \nbedrooms:neighborhoodRoseville 0.011911 * \nbedrooms:neighborhoodSan Carlos 0.111073 \nbedrooms:neighborhoodScripps Ranch 0.656951 \nbedrooms:neighborhoodSerra Mesa 0.216272 \nbedrooms:neighborhoodSouth Park 0.475443 \nbedrooms:neighborhoodUniversity City 0.302694 \nbedrooms:neighborhoodWest University Heights 0.907079 \nbeds:neighborhoodBalboa Park 0.770871 \nbeds:neighborhoodBay Ho 0.780950 \nbeds:neighborhoodBay Park 0.918856 \nbeds:neighborhoodCarmel Valley 0.256631 \nbeds:neighborhoodCity Heights West 0.562357 \nbeds:neighborhoodClairemont Mesa 0.070055 . \nbeds:neighborhoodCollege Area 0.006717 ** \nbeds:neighborhoodCore 0.307987 \nbeds:neighborhoodCortez Hill 0.361302 \nbeds:neighborhoodDel Mar Heights 0.764449 \nbeds:neighborhoodEast Village 1.86e-05 ***\nbeds:neighborhoodGaslamp Quarter 0.470435 \nbeds:neighborhoodGrant Hill 0.007960 ** \nbeds:neighborhoodGrantville 0.976078 \nbeds:neighborhoodKensington 0.975344 \nbeds:neighborhoodLa Jolla 3.10e-06 ***\nbeds:neighborhoodLa Jolla Village 0.220739 \nbeds:neighborhoodLinda Vista 0.662889 \nbeds:neighborhoodLittle Italy 1.07e-06 ***\nbeds:neighborhoodLoma Portal 0.825463 \nbeds:neighborhoodMarina 0.064324 . \nbeds:neighborhoodMidtown 8.90e-05 ***\nbeds:neighborhoodMidtown District 0.143173 \nbeds:neighborhoodMira Mesa 0.388579 \nbeds:neighborhoodMission Bay 0.000546 ***\nbeds:neighborhoodMission Valley 0.756247 \nbeds:neighborhoodMoreno Mission 0.093375 . \nbeds:neighborhoodNormal Heights 0.002763 ** \nbeds:neighborhoodNorth Clairemont 0.748634 \nbeds:neighborhoodNorth Hills 0.000558 ***\nbeds:neighborhoodNorthwest 0.373186 \nbeds:neighborhoodOcean Beach 0.191957 \nbeds:neighborhoodOld Town 0.041051 * \nbeds:neighborhoodOtay Ranch 0.783359 \nbeds:neighborhoodPacific Beach 0.279326 \nbeds:neighborhoodPark West 0.437885 \nbeds:neighborhoodRancho Bernadino 0.003373 ** \nbeds:neighborhoodRancho Penasquitos 0.900966 \nbeds:neighborhoodRoseville 0.329465 \nbeds:neighborhoodSan Carlos 0.001019 ** \nbeds:neighborhoodScripps Ranch 0.073072 . \nbeds:neighborhoodSerra Mesa 0.120848 \nbeds:neighborhoodSouth Park 0.854878 \nbeds:neighborhoodUniversity City 0.048378 * \nbeds:neighborhoodWest University Heights 0.136320 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.478 on 5885 degrees of freedom\nMultiple R-squared: 0.6622, Adjusted R-squared: 0.6494 \nF-statistic: 51.51 on 224 and 5885 DF, p-value: < 2.2e-16\n\n\nThis allows us to get a separate constant term and estimate of the impact of each variable for every neighborhood", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#spatial-dependence", "href": "06-spatial-econometrics.html#spatial-dependence", "title": "6  Spatial Econometrics", - "section": "6.6 Spatial dependence", - "text": "6.6 Spatial dependence\nAs we have just discussed, SH is about effects of phenomena that are explicitly linked to geography and that hence cause spatial variation and clustering of values. This encompasses many of the kinds of spatial effects we may be interested in when we fit linear regressions. However, in other cases, our interest is on the effect of the spatial configuration of the observations, and the extent to which that has an effect on the outcome we are considering. For example, we might think that the price of a house not only depends on the number of bathrooms it has but, if we take number of bathrooms as a proxy for size and status, also whether it is surrounded by other houses with many bathrooms. This kind of spatial effect is fundamentally different from SH in that is it not related to inherent characteristics of the geography but relates to the characteristics of the observations in our dataset and, specially, to their spatial arrangement. We call this phenomenon by which the values of observations are related to each other through distance spatial dependence (Anselin 1988).\nSpatial Weights\nThere are several ways to introduce spatial dependence in an econometric framework, with varying degrees of econometric sophistication (see Anselin 2003 for a good overview). Common to all of them however is the way space is formally encapsulated: through spatial weights matrices (\\(W\\))3 These are \\(NxN\\) matrices with zero diagonals and every \\(w_{ij}\\) cell with a value that represents the degree of spatial connectivity/interaction between observations \\(i\\) and \\(j\\). If they are not connected at all, \\(w_{ij}=0\\), otherwise \\(w_{ij}>0\\) and we call \\(i\\) and \\(j\\) neighbors. The exact value in the latter case depends on the criterium we use to define neighborhood relations. These matrices also tend to be row-standardized so the sum of each row equals to one.\nA related concept to spatial weight matrices is that of spatial lag. This is an operator that multiplies a given variable \\(y\\) by a spatial weight matrix:\n\\[\ny_{lag} = W y\n\\]\nIf \\(W\\) is row-standardized, \\(y_{lag}\\) is effectively the average value of \\(y\\) in the neighborhood of each observation. The individual notation may help clarify this:\n\\[\ny_{lag-i} = \\displaystyle \\sum_j w_{ij} y_j\n\\]\nwhere \\(y_{lag-i}\\) is the spatial lag of variable \\(y\\) at location \\(i\\), and \\(j\\) sums over the entire dataset. If \\(W\\) is row-standardized, \\(y_{lag-i}\\) becomes an average of \\(y\\) weighted by the spatial criterium defined in \\(W\\).\nGiven that spatial weights matrices are not the focus of this tutorial, we will stick to a very simple case. Since we are dealing with points, we will use \\(K\\)-nn weights, which take the \\(k\\) nearest neighbors of each observation as neighbors and assign a value of one, assigning everyone else a zero. We will use \\(k=50\\) to get a good degree of variation and sensible results.\n\n# Create knn list of each house\nhnn <- db %>%\n st_coordinates() %>%\n as.matrix() %>%\n knearneigh(k = 50)\n# Create nb object\nhnb <- knn2nb(hnn)\n# Create spatial weights matrix (note it row-standardizes by default)\nhknn <- nb2listw(hnb)\n\nWe can inspect the weights created by simply typing the name of the object:\n\nhknn\n\nCharacteristics of weights list object:\nNeighbour list object:\nNumber of regions: 6110 \nNumber of nonzero links: 305500 \nPercentage nonzero weights: 0.8183306 \nAverage number of links: 50 \nNon-symmetric neighbours list\n\nWeights style: W \nWeights constants summary:\n n nn S0 S1 S2\nW 6110 37332100 6110 220.5032 24924.44\n\n\nExogenous spatial effects\nLet us come back to the house price example we have been working with. So far, we have hypothesized that the price of an AirBnb property in San Diego can be explained using information about its own characteristics, and the neighbourhood it belongs to. However, we can hypothesise that the price of a house is also affected by the characteristics of the houses surrounding it. Considering it as a proxy for larger and more luxurious houses, we will use the number of bathrooms of neighboring houses as an additional explanatory variable. This represents the most straightforward way to introduce spatial dependence in a regression, by considering not only a given explanatory variable, but also its spatial lag.\nIn our example case, in addition to including the number of bathrooms of the property, we will include its spatial lag. In other words, we will be saying that it is not only the number of bathrooms in a house but also that of the surrounding properties that helps explain the final price at which a house is advertised for. Mathematically, this implies estimating the following model:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i+ \\beta_5 Bath_{lag-i} + \\epsilon_i\n\\]\nLet us first compute the spatial lag of bathrooms:\n\ndb$w_bathrooms <- lag.listw(hknn, db$bathrooms)\n\nAnd then we can include it in our previous specification. Note that we apply the log to the lag, not the reverse:\n\nm5 <- lm(\n 'log_price ~ accommodates + bedrooms + beds + bathrooms + w_bathrooms',\n db\n)\n\nsummary(m5)\n\n\nCall:\nlm(formula = \"log_price ~ accommodates + bedrooms + beds + bathrooms + w_bathrooms\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.8869 -0.3243 -0.0206 0.2931 3.5132 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.579448 0.032337 110.692 <2e-16 ***\naccommodates 0.173226 0.005233 33.100 <2e-16 ***\nbedrooms 0.103116 0.012327 8.365 <2e-16 ***\nbeds -0.075071 0.007787 -9.641 <2e-16 ***\nbathrooms 0.117268 0.012507 9.376 <2e-16 ***\nw_bathrooms 0.353021 0.023572 14.976 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.5271 on 6104 degrees of freedom\nMultiple R-squared: 0.574, Adjusted R-squared: 0.5736 \nF-statistic: 1645 on 5 and 6104 DF, p-value: < 2.2e-16\n\n\nAs we can see, the lag is not only significative and positive, but its effect seems to be even larger that that of the property itself. Taken literally, this implies that the average number of bathrooms in AirBnb’s nearby has a larger effect on the final price of a given AirBnb than its own number of bathrooms. There are several ways to interpret this. One is that, if we take the spatial lag of bathrooms, as we said above, to be a proxy for the types of houses surrounding a property, this is probably a better predictor of how wealthy an area is than the number of bathrooms of a single property, which is more variable. If we also assume that the area where an AirBnb is located has a bigger effect on price than the number of bathrooms, we can start seeing an answer to the apparent puzzle.\nA note on more advanced spatial regression\nIntroducing a spatial lag of an explanatory variable, as we have just seen, is the most straightforward way of incorporating the notion of spatial dependence in a linear regression framework. It does not require additional changes, it can be estimated with OLS, and the interpretation is rather similar to interpreting non-spatial variables. The field of spatial econometrics however is a much broader one and has produced over the last decades many techniques to deal with spatial effects and spatial dependence in different ways. Although this might be an over simplification, one can say that most of such efforts for the case of a single cross-section are focused on two main variations: the spatial lag and the spatial error model. Both are similar to the case we have seen in that they are based on the introduction of a spatial lag, but they differ in the component of the model they modify and affect.\nThe spatial lag model introduces a spatial lag of the dependent variable. In the example we have covered, this would translate into:\n\\[\n\\log(P_i) = \\alpha + \\rho \\log(P_i) + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nAlthough it might not seem very different from the previous equation, this model violates the exogeneity assumption, crucial for OLS to work.\nEqually, the spatial error model includes a spatial lag in the error term of the equation:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + u_i\n\\]\n\\[\nu_i = u_{lag-i} + \\epsilon_i\n\\]\nAgain, although similar, one can show this specification violates the assumptions about the error term in a classical OLS model.\nBoth the spatial lag and error model violate some of the assumptions on which OLS relies and thus render the technique unusable. Much of the efforts have thus focused on coming up with alternative methodologies that allow unbiased, robust, and efficient estimation of such models. A survey of those is beyond the scope of this note, but the interested reader is referred to Anselin (1988), Anselin (2003), and Anselin and Rey (2014) for further reference." + "section": "\n6.6 Spatial dependence", + "text": "6.6 Spatial dependence\nAs we have just discussed, SH is about effects of phenomena that are explicitly linked to geography and that hence cause spatial variation and clustering of values. This encompasses many of the kinds of spatial effects we may be interested in when we fit linear regressions. However, in other cases, our interest is on the effect of the spatial configuration of the observations, and the extent to which that has an effect on the outcome we are considering. For example, we might think that the price of a house not only depends on the number of bathrooms it has but, if we take number of bathrooms as a proxy for size and status, also whether it is surrounded by other houses with many bathrooms. This kind of spatial effect is fundamentally different from SH in that is it not related to inherent characteristics of the geography but relates to the characteristics of the observations in our dataset and, specially, to their spatial arrangement. We call this phenomenon by which the values of observations are related to each other through distance spatial dependence (Anselin 1988).\nSpatial Weights\nThere are several ways to introduce spatial dependence in an econometric framework, with varying degrees of econometric sophistication (see Anselin 2003 for a good overview). Common to all of them however is the way space is formally encapsulated: through spatial weights matrices (\\(W\\))2 These are \\(NxN\\) matrices with zero diagonals and every \\(w_{ij}\\) cell with a value that represents the degree of spatial connectivity/interaction between observations \\(i\\) and \\(j\\). If they are not connected at all, \\(w_{ij}=0\\), otherwise \\(w_{ij}>0\\) and we call \\(i\\) and \\(j\\) neighbors. The exact value in the latter case depends on the criterium we use to define neighborhood relations. These matrices also tend to be row-standardized so the sum of each row equals to one.\n2 If you need to refresh your knowledge on spatial weight matrices, check Block E of Arribas-Bel (2019); Chapter 4 of Rey, Arribas-Bel, and Wolf (2023); or the Spatial Weights Section of Rowe (2022).A related concept to spatial weight matrices is that of spatial lag. This is an operator that multiplies a given variable \\(y\\) by a spatial weight matrix:\n\\[\ny_{lag} = W y\n\\]\nIf \\(W\\) is row-standardized, \\(y_{lag}\\) is effectively the average value of \\(y\\) in the neighborhood of each observation. The individual notation may help clarify this:\n\\[\ny_{lag-i} = \\displaystyle \\sum_j w_{ij} y_j\n\\]\nwhere \\(y_{lag-i}\\) is the spatial lag of variable \\(y\\) at location \\(i\\), and \\(j\\) sums over the entire dataset. If \\(W\\) is row-standardized, \\(y_{lag-i}\\) becomes an average of \\(y\\) weighted by the spatial criterium defined in \\(W\\).\nGiven that spatial weights matrices are not the focus of this tutorial, we will stick to a very simple case. Since we are dealing with points, we will use \\(K\\)-nn weights, which take the \\(k\\) nearest neighbors of each observation as neighbors and assign a value of one, assigning everyone else a zero. We will use \\(k=50\\) to get a good degree of variation and sensible results.\n\n# Create knn list of each house\nhnn <- db %>%\n st_coordinates() %>%\n as.matrix() %>%\n knearneigh(k = 50)\n# Create nb object\nhnb <- knn2nb(hnn)\n# Create spatial weights matrix (note it row-standardizes by default)\nhknn <- nb2listw(hnb)\n\nWe can inspect the weights created by simply typing the name of the object:\n\nhknn\n\nCharacteristics of weights list object:\nNeighbour list object:\nNumber of regions: 6110 \nNumber of nonzero links: 305500 \nPercentage nonzero weights: 0.8183306 \nAverage number of links: 50 \nNon-symmetric neighbours list\n\nWeights style: W \nWeights constants summary:\n n nn S0 S1 S2\nW 6110 37332100 6110 220.5032 24924.44\n\n\nExogenous spatial effects\nLet us come back to the house price example we have been working with. So far, we have hypothesized that the price of an AirBnb property in San Diego can be explained using information about its own characteristics, and the neighbourhood it belongs to. However, we can hypothesise that the price of a house is also affected by the characteristics of the houses surrounding it. Considering it as a proxy for larger and more luxurious houses, we will use the number of bathrooms of neighboring houses as an additional explanatory variable. This represents the most straightforward way to introduce spatial dependence in a regression, by considering not only a given explanatory variable, but also its spatial lag.\nIn our example case, in addition to including the number of bathrooms of the property, we will include its spatial lag. In other words, we will be saying that it is not only the number of bathrooms in a house but also that of the surrounding properties that helps explain the final price at which a house is advertised for. Mathematically, this implies estimating the following model:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i+ \\beta_5 Bath_{lag-i} + \\epsilon_i\n\\]\nLet us first compute the spatial lag of bathrooms:\n\ndb$w_bathrooms <- lag.listw(hknn, db$bathrooms)\n\nAnd then we can include it in our previous specification. Note that we apply the log to the lag, not the reverse:\n\nm5 <- lm(\n 'log_price ~ accommodates + bedrooms + beds + bathrooms + w_bathrooms',\n db\n)\n\nsummary(m5)\n\n\nCall:\nlm(formula = \"log_price ~ accommodates + bedrooms + beds + bathrooms + w_bathrooms\", \n data = db)\n\nResiduals:\n Min 1Q Median 3Q Max \n-2.8869 -0.3243 -0.0206 0.2931 3.5132 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 3.579448 0.032337 110.692 <2e-16 ***\naccommodates 0.173226 0.005233 33.100 <2e-16 ***\nbedrooms 0.103116 0.012327 8.365 <2e-16 ***\nbeds -0.075071 0.007787 -9.641 <2e-16 ***\nbathrooms 0.117268 0.012507 9.376 <2e-16 ***\nw_bathrooms 0.353021 0.023572 14.976 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.5271 on 6104 degrees of freedom\nMultiple R-squared: 0.574, Adjusted R-squared: 0.5736 \nF-statistic: 1645 on 5 and 6104 DF, p-value: < 2.2e-16\n\n\nAs we can see, the lag is not only significative and positive, but its effect seems to be even larger that that of the property itself. Taken literally, this implies that the average number of bathrooms in AirBnb’s nearby has a larger effect on the final price of a given AirBnb than its own number of bathrooms. There are several ways to interpret this. One is that, if we take the spatial lag of bathrooms, as we said above, to be a proxy for the types of houses surrounding a property, this is probably a better predictor of how wealthy an area is than the number of bathrooms of a single property, which is more variable. If we also assume that the area where an AirBnb is located has a bigger effect on price than the number of bathrooms, we can start seeing an answer to the apparent puzzle.\nA note on more advanced spatial regression\nIntroducing a spatial lag of an explanatory variable, as we have just seen, is the most straightforward way of incorporating the notion of spatial dependence in a linear regression framework. It does not require additional changes, it can be estimated with OLS, and the interpretation is rather similar to interpreting non-spatial variables. The field of spatial econometrics however is a much broader one and has produced over the last decades many techniques to deal with spatial effects and spatial dependence in different ways. Although this might be an over simplification, one can say that most of such efforts for the case of a single cross-section are focused on two main variations: the spatial lag and the spatial error model. Both are similar to the case we have seen in that they are based on the introduction of a spatial lag, but they differ in the component of the model they modify and affect.\nThe spatial lag model introduces a spatial lag of the dependent variable. In the example we have covered, this would translate into:\n\\[\n\\log(P_i) = \\alpha + \\rho \\log(P_i) + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\nAlthough it might not seem very different from the previous equation, this model violates the exogeneity assumption, crucial for OLS to work.\nEqually, the spatial error model includes a spatial lag in the error term of the equation:\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + u_i\n\\]\n\\[\nu_i = u_{lag-i} + \\epsilon_i\n\\]\nAgain, although similar, one can show this specification violates the assumptions about the error term in a classical OLS model.\nBoth the spatial lag and error model violate some of the assumptions on which OLS relies and thus render the technique unusable. Much of the efforts have thus focused on coming up with alternative methodologies that allow unbiased, robust, and efficient estimation of such models. A survey of those is beyond the scope of this note, but the interested reader is referred to Anselin (1988), Anselin (2003), and Anselin and Rey (2014) for further reference.", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#predicting-house-prices", "href": "06-spatial-econometrics.html#predicting-house-prices", "title": "6  Spatial Econometrics", - "section": "6.7 Predicting house prices", - "text": "6.7 Predicting house prices\nSo far, we have seen how to exploit the output of a regression model to evaluate the role different variables play in explaining another one of interest. However, once fit, a model can also be used to obtain predictions of the dependent variable given a new set of values for the explanatory variables. We will finish this session by dipping our toes in predicting with linear models.\nThe core idea is that once you have estimates for the way in which the explanatory variables can be combined to explain the dependent one, you can plug new values on the explanatory side of the model and combine them following the model estimates to obtain predictions. In the example we have worked with, you can imagine this application would be useful to obtain valuations of a house, given we know its characteristics.\nConceptually, predicting in linear regression models involves using the estimates of the parameters to obtain a value for the dependent variable:\n\\[\n\\bar{\\log(P_i)} = \\bar{\\alpha} + \\bar{\\beta_1} Acc_i^* + \\bar{\\beta_2 Bath_i^*} + \\bar{\\beta_3} Bedr_i^* + \\bar{\\beta_4} Beds_i^*\n\\] where \\(\\bar{\\log{P_i}}\\) is our predicted value, and we include the \\(\\bar{}\\) sign to note that it is our estimate obtained from fitting the model. We use the \\(^*\\) sign to note that those can be new values for the explanatory variables, not necessarily those used to fit the model.\nTechnically speaking, prediction in linear models is relatively streamlined in R. Suppose we are given data for a new house which is to be put on the AirBnb platform. We know it accommodates four people, and has two bedrooms, three beds, and one bathroom. We also know that the surrounding properties have, on average, 1.5 bathrooms. Let us record the data first:\n\nnew.house <- data.frame(\n accommodates = 4, \n bedrooms = 2,\n beds = 3,\n bathrooms = 1,\n w_bathrooms = 1.5\n)\n\nTo obtain the prediction for its price, we can use the predict method:\n\nnew.price <- predict(m5, new.house)\nnew.price\n\n 1 \n4.900168 \n\n\nNow remember we were using the log of the price as dependent variable. If we want to recover the actual price of the house, we need to take its exponent:\n\nexp(new.price)\n\n 1 \n134.3123 \n\n\nAccording to our model, the house would be worth $134.3123448." + "section": "\n6.7 Predicting house prices", + "text": "6.7 Predicting house prices\nSo far, we have seen how to exploit the output of a regression model to evaluate the role different variables play in explaining another one of interest. However, once fit, a model can also be used to obtain predictions of the dependent variable given a new set of values for the explanatory variables. We will finish this session by dipping our toes in predicting with linear models.\nThe core idea is that once you have estimates for the way in which the explanatory variables can be combined to explain the dependent one, you can plug new values on the explanatory side of the model and combine them following the model estimates to obtain predictions. In the example we have worked with, you can imagine this application would be useful to obtain valuations of a house, given we know its characteristics.\nConceptually, predicting in linear regression models involves using the estimates of the parameters to obtain a value for the dependent variable:\n\\[\n\\log(\\bar{P_i}) = \\bar{\\alpha} + \\bar{\\beta_1} Acc_i^* + \\bar{\\beta_2} Bath_i^* + \\bar{\\beta_3} Bedr_i^* + \\bar{\\beta_4} Beds_i^*\n\\] where \\(\\log(\\bar{P_i})\\) is our predicted value, and we include the bar sign to note that it is our estimate obtained from fitting the model. We use the \\(^*\\) sign to note that those can be new values for the explanatory variables, not necessarily those used to fit the model.\nTechnically speaking, prediction in linear models is relatively streamlined in R. Suppose we are given data for a new house which is to be put on the AirBnb platform. We know it accommodates four people, and has two bedrooms, three beds, and one bathroom. We also know that the surrounding properties have, on average, 1.5 bathrooms. Let us record the data first:\n\nnew.house <- data.frame(\n accommodates = 4, \n bedrooms = 2,\n beds = 3,\n bathrooms = 1,\n w_bathrooms = 1.5\n)\n\nTo obtain the prediction for its price, we can use the predict method:\n\nnew.price <- predict(m5, new.house)\nnew.price\n\n 1 \n4.900168 \n\n\nNow remember we were using the log of the price as dependent variable. If we want to recover the actual price of the house, we need to take its exponent:\n\nexp(new.price)\n\n 1 \n134.3123 \n\n\nAccording to our model, the house would be worth $134.3123448.", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { "objectID": "06-spatial-econometrics.html#questions", "href": "06-spatial-econometrics.html#questions", "title": "6  Spatial Econometrics", - "section": "6.8 Questions", - "text": "6.8 Questions\nWe will be using again the Madrid AirBnb dataset: \n\nmad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\n\ncolnames(mad_abb)\n\n [1] \"price\" \"price_usd\" \"log1p_price_usd\" \"accommodates\" \n [5] \"bathrooms_text\" \"bathrooms\" \"bedrooms\" \"beds\" \n [9] \"neighbourhood\" \"room_type\" \"property_type\" \"WiFi\" \n[13] \"Coffee\" \"Gym\" \"Parking\" \"km_to_retiro\" \n[17] \"geom\" \n\n\n\nIn addition to those we have already seen, the columns to use here are:\n\nneighbourhood: a column with the name of the neighbourhood in which the property is located\n\nWith this at hand, answer the following questions:\n\nFit a baseline model with only property characteristics explaining the log of price\n\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nAugment the model with fixed effects at the neighbourhood level\n\n\\[\n\\log(P_i) = \\alpha_r + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\n[Optional] Augment the model with spatial regimes at the neighbourhood level:\n\n\\[\n\\log(P_i) = \\alpha_r + \\beta_{r1} Acc_i + \\beta_{r2} Bath_i + \\beta_{r3} Bedr_i + \\beta_{r4} Beds_i + \\epsilon_{ri}\n\\]\n\nFit a model that augments the baseline in 1. with the spatial lag of a variable you consider interesting. Motivate this choice. Note that to complete this, you will need to also generate a spatial weights matrix.\n\nIn each instance, provide a brief interpretation (no more thana few lines for each) that demonstrates your understanding of theunderlying concepts behind your approach. \n\n\n\n\nAnselin, Luc. 1988. Spatial Econometrics: Methods and Models. Vol. 4. Springer Science & Business Media.\n\n\n———. 2003. “Spatial Externalities, Spatial Multipliers, and Spatial Econometrics.” International Regional Science Review 26 (2): 153–66.\n\n\n———. 2007. “Spatial Regression Analysis in r–a Workbook.” Center for Spatially Integrated Social Science. http://csiss.org/GISPopSci/workshops/2011/PSU/readings/W15_Anselin2007.pdf.\n\n\nAnselin, Luc, and Sergio J. Rey. 2014. Modern Spatial Econometrics in Practice: A Guide to GeoDa, GeoDaSpace and PySAL. GeoDa Press LLC.\n\n\nArribas-Bel, Dani. 2014. “Spatial Data, Analysis, and Regression-a Mini Course.” REGION 1 (1): R1. http://darribas.org/sdar_mini.\n\n\n———. 2019. “A Course on Geographic Data Science.” The Journal of Open Source Education 2 (14). https://doi.org/https://doi.org/10.21105/jose.00042.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nGibbons, Stephen, Henry G Overman, and Eleonora Patacchini. 2014. “Spatial Methods.”\n\n\nRey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. forthcoming. Geographic Data Science with PySAL and the PyData Stack. CRC press.\n\n\nRowe, Francisco. 2022. “Introduction to Geographic Data Science.” Open Science Framework, August. https://doi.org/10.17605/OSF.IO/VHY2P." + "section": "\n6.8 Questions", + "text": "6.8 Questions\nWe will be using again the Madrid AirBnb dataset: \n\nmad_abb <- st_read('./data/assignment_1_madrid/madrid_abb.gpkg')\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\n\ncolnames(mad_abb)\n\n [1] \"price\" \"price_usd\" \"log1p_price_usd\" \"accommodates\" \n [5] \"bathrooms_text\" \"bathrooms\" \"bedrooms\" \"beds\" \n [9] \"neighbourhood\" \"room_type\" \"property_type\" \"WiFi\" \n[13] \"Coffee\" \"Gym\" \"Parking\" \"km_to_retiro\" \n[17] \"geom\" \n\n\n\nIn addition to those we have already seen, the columns to use here are:\n\n\nneighbourhood: a column with the name of the neighbourhood in which the property is located\n\nWith this at hand, answer the following questions:\n\nFit a baseline model with only property characteristics explaining the log of price\n\n\\[\n\\log(P_i) = \\alpha + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\nAugment the model with fixed effects at the neighbourhood level\n\n\\[\n\\log(P_i) = \\alpha_r + \\beta_1 Acc_i + \\beta_2 Bath_i + \\beta_3 Bedr_i + \\beta_4 Beds_i + \\epsilon_i\n\\]\n\n[Optional] Augment the model with spatial regimes at the neighbourhood level:\n\n\\[\n\\log(P_i) = \\alpha_r + \\beta_{r1} Acc_i + \\beta_{r2} Bath_i + \\beta_{r3} Bedr_i + \\beta_{r4} Beds_i + \\epsilon_{ri}\n\\]\n\nFit a model that augments the baseline in 1. with the spatial lag of a variable you consider interesting. Motivate this choice. Note that to complete this, you will need to also generate a spatial weights matrix.\n\nIn each instance, provide a brief interpretation (no more thana few lines for each) that demonstrates your understanding of theunderlying concepts behind your approach. \n\n\n\n\n\n\nAnselin, Luc. 1988. Spatial Econometrics: Methods and Models. Vol. 4. Springer Science & Business Media.\n\n\n———. 2003. “Spatial Externalities, Spatial Multipliers, and Spatial Econometrics.” International Regional Science Review 26 (2): 153–66.\n\n\n———. 2007. “Spatial Regression Analysis in r–a Workbook.” Center for Spatially Integrated Social Science. http://csiss.org/GISPopSci/workshops/2011/PSU/readings/W15_Anselin2007.pdf.\n\n\nAnselin, Luc, and Sergio J. Rey. 2014. Modern Spatial Econometrics in Practice: A Guide to GeoDa, GeoDaSpace and PySAL. GeoDa Press LLC.\n\n\nArribas-Bel, Dani. 2014. “Spatial Data, Analysis, and Regression-a Mini Course.” REGION 1 (1): R1. http://darribas.org/sdar_mini.\n\n\n———. 2019. “A Course on Geographic Data Science.” The Journal of Open Source Education 2 (14). https://doi.org/https://doi.org/10.21105/jose.00042.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nGibbons, Stephen, Henry G Overman, and Eleonora Patacchini. 2014. “Spatial Methods.”\n\n\nRey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. 2023. Geographic Data Science with PySAL and the PyData Stack. CRC press.\n\n\nRowe, Francisco. 2022. “Introduction to Geographic Data Science.” Open Science Framework, August. https://doi.org/10.17605/OSF.IO/VHY2P.", + "crumbs": [ + "6  Spatial Econometrics" + ] }, { - "objectID": "07-multilevel-01.html#dependencies", - "href": "07-multilevel-01.html#dependencies", + "objectID": "07-multilevel-01.html", + "href": "07-multilevel-01.html", "title": "7  Multilevel Modelling - Part 1", - "section": "7.1 Dependencies", - "text": "7.1 Dependencies\nThis chapter uses the following libraries which are listed in the [Dependency list] Section of Chapter 1:\n\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(RColorBrewer) \n# More colour palettes\nlibrary(viridis) # nice colour schemes\n# Fitting multilevel models\nlibrary(lme4)\n# Tools for extracting information generated by lme4\nlibrary(merTools)\n# Exportable regression tables\nlibrary(jtools)\nlibrary(stargazer)\nlibrary(sjPlot)" + "section": "", + "text": "7.1 Dependencies\nWe will use the following dependencies\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Spatial data manitulation\nlibrary(sf) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(viridis) \n# Fitting multilevel models\nlibrary(lme4)\n# Tools for extracting information generated by lme4\nlibrary(merTools)\n# Exportable regression tables\nlibrary(jtools)", + "crumbs": [ + "7  Multilevel Modelling - Part 1" + ] }, { "objectID": "07-multilevel-01.html#data", "href": "07-multilevel-01.html#data", "title": "7  Multilevel Modelling - Part 1", - "section": "7.2 Data", - "text": "7.2 Data\nFor this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic attributes of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1\nLet us read the data:\n\n# clean workspace\nrm(list=ls())\n# read data\noa_shp <- st_read(\"data/mlm/OA.shp\")\n\nWe can now attach and visualise the structure of the data.\n\n# attach data frame\nattach(oa_shp)\n\nThe following object is masked from package:viridis:\n\n unemp\n\n# sort data by oa\noa_shp <- oa_shp[order(oa_cd),]\nhead(oa_shp)\n\nSimple feature collection with 6 features and 19 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335056 ymin: 389163 xmax: 336155 ymax: 389642\nProjected CRS: Transverse_Mercator\n oa_cd lsoa_cd msoa_cd lad_cd ward_nm dstrt_nm cnty_nm\n1 E00032987 E01006515 E02001383 E08000012 Riverside Liverpool Merseyside\n2 E00032988 E01006514 E02001383 E08000012 Princes Park Liverpool Merseyside\n3 E00032989 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n4 E00032990 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n5 E00032991 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n6 E00032992 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n cntry_nm pop age_60 unemp lat long males lt_ill\n1 England 198 0.11616162 0.1130435 53.39821 -2.976786 46.46465 19.19192\n2 England 348 0.16954023 0.1458333 53.39813 -2.969072 58.33333 33.62069\n3 England 333 0.09009009 0.1049724 53.39778 -2.965290 64.26426 23.72372\n4 England 330 0.15151515 0.1329787 53.39802 -2.963597 59.69697 23.03030\n5 England 320 0.04687500 0.1813725 53.39706 -2.968030 60.62500 25.00000\n6 England 240 0.05833333 0.2519685 53.39679 -2.966494 57.91667 28.33333\n Bhealth VBhealth no_qual manprof geometry\n1 6.565657 1.515152 24.69136 7.643312 MULTIPOLYGON (((335187 3894...\n2 10.344828 1.436782 14.84848 13.375796 MULTIPOLYGON (((335834 3895...\n3 6.606607 2.102102 15.38462 10.204082 MULTIPOLYGON (((335975.2 38...\n4 5.151515 2.424242 17.91531 15.224913 MULTIPOLYGON (((336030.8 38...\n5 8.750000 2.187500 12.58278 11.333333 MULTIPOLYGON (((335804.9 38...\n6 6.666667 2.916667 27.47748 5.479452 MULTIPOLYGON (((335804.9 38...\n\n\n\n\n\nFig. 1. Data Structure.\n\n\nThe data are hierarchically structured: OAs nested within LSOAs; LSOAs nested within MSOAs; and, MSOAs nested within LADs. Observations nested within higher geographical units may be correlated.\nThis is one type of hierarchical structure. There is a range of data structures:\n\nStrict nested data structures eg. an individual unit is nested within only one higher unit\nRepeated measures structures eg. various measurements for an individual unit\nCrossed classified structures eg. individuals may work and live in different neighbourhoods\nMultiple membership structure eg. individuals may have two different work places\n\nWhy should we care about the structure of the data?\n\nDraw correct statistical inference: Failing to recognise hierarchical structures will lead to underestimated standard errors of regression coefficients and an overstatement of statistical significance. Standard errors for the coefficients of higher-level predictor variables will be the most affected by ignoring grouping.\nLink context to individual units: We can link and understand the extent of group effects on individual outcomes eg. how belonging to a certain socio-economic group influences on future career opportunities.\nSpatial dependency: Recognising the hierarchical structure of data may help mitigate the effects of severe spatial autocorrelation.\n\nQuickly, let us get a better idea about the data and look at the number of OAs nested within LSOAs and MSOAs\n\n# mean of nested OAs within LSOAs and MSOAs\nlsoa_cd %>% table() %>%\n mean() %>%\n round(, 2)\n\n[1] 5\n\nmsoa_cd %>% table() %>%\n mean() %>%\n round(, 2)\n\n[1] 26\n\n# number of OAs nested within LSOAs and MSOAs\nlsoa_cd %>% table() %>%\n sort() %>%\n plot()\n\n\n\nmsoa_cd %>% table() %>%\n sort() %>%\n plot()" + "section": "\n7.2 Data", + "text": "7.2 Data\nFor this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic attributes of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1\n1 Read the file in R by executing read_tsv(\"data/mlm/readme.txt\") . Ensure the library readr is installed before running read_tsv.Let us read the data:\n\n# read data\noa_shp <- st_read(\"data/mlm/OA.shp\")\n\nReading layer `OA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/OA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 1584 features and 19 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 332390.2 ymin: 379748.5 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n\nWe can now attach and visualise the structure of the data.\n\n# attach data frame\nattach(oa_shp)\n\n# sort data by oa\noa_shp <- oa_shp[order(oa_cd),]\nhead(oa_shp)\n\nSimple feature collection with 6 features and 19 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335056 ymin: 389163 xmax: 336155 ymax: 389642\nProjected CRS: Transverse_Mercator\n oa_cd lsoa_cd msoa_cd lad_cd ward_nm dstrt_nm cnty_nm\n1 E00032987 E01006515 E02001383 E08000012 Riverside Liverpool Merseyside\n2 E00032988 E01006514 E02001383 E08000012 Princes Park Liverpool Merseyside\n3 E00032989 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n4 E00032990 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n5 E00032991 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n6 E00032992 E01033768 E02001383 E08000012 Princes Park Liverpool Merseyside\n cntry_nm pop age_60 unemp lat long males lt_ill\n1 England 198 0.11616162 0.1130435 53.39821 -2.976786 46.46465 19.19192\n2 England 348 0.16954023 0.1458333 53.39813 -2.969072 58.33333 33.62069\n3 England 333 0.09009009 0.1049724 53.39778 -2.965290 64.26426 23.72372\n4 England 330 0.15151515 0.1329787 53.39802 -2.963597 59.69697 23.03030\n5 England 320 0.04687500 0.1813725 53.39706 -2.968030 60.62500 25.00000\n6 England 240 0.05833333 0.2519685 53.39679 -2.966494 57.91667 28.33333\n Bhealth VBhealth no_qual manprof geometry\n1 6.565657 1.515152 24.69136 7.643312 MULTIPOLYGON (((335187 3894...\n2 10.344828 1.436782 14.84848 13.375796 MULTIPOLYGON (((335834 3895...\n3 6.606607 2.102102 15.38462 10.204082 MULTIPOLYGON (((335975.2 38...\n4 5.151515 2.424242 17.91531 15.224913 MULTIPOLYGON (((336030.8 38...\n5 8.750000 2.187500 12.58278 11.333333 MULTIPOLYGON (((335804.9 38...\n6 6.666667 2.916667 27.47748 5.479452 MULTIPOLYGON (((335804.9 38...\n\n\n\n\nFig. 1. Data Structure.\n\nThe data are hierarchically structured: OAs nested within LSOAs; LSOAs nested within MSOAs; and, MSOAs nested within LADs. Observations nested within higher geographical units may be correlated.\nThis is one type of hierarchical structure. There is a range of data structures:\n\nStrict nested data structures eg. an individual unit is nested within only one higher unit\nRepeated measures structures eg. various measurements for an individual unit\nCrossed classified structures eg. individuals may work and live in different neighbourhoods\nMultiple membership structure eg. individuals may have two different work places\n\nWhy should we care about the structure of the data?\n\nDraw correct statistical inference: Failing to recognise hierarchical structures will lead to underestimated standard errors of regression coefficients and an overstatement of statistical significance. Standard errors for the coefficients of higher-level predictor variables will be the most affected by ignoring grouping.\nLink context to individual units: We can link and understand the extent of group effects on individual outcomes eg. how belonging to a certain socio-economic group influences on future career opportunities.\nSpatial dependency: Recognising the hierarchical structure of data may help mitigate the effects of severe spatial autocorrelation.\n\nQuickly, let us get a better idea about the data and look at the number of OAs nested within LSOAs and MSOAs\n\n# mean of nested OAs within LSOAs and MSOAs\nlsoa_cd %>% table() %>%\n mean() %>%\n round(, 2)\n\n[1] 5\n\nmsoa_cd %>% table() %>%\n mean() %>%\n round(, 2)\n\n[1] 26\n\n# number of OAs nested within LSOAs and MSOAs\nlsoa_cd %>% table() %>%\n sort() %>%\n plot()\n\n\n\n\n\n\nmsoa_cd %>% table() %>%\n sort() %>%\n plot()", + "crumbs": [ + "7  Multilevel Modelling - Part 1" + ] }, { "objectID": "07-multilevel-01.html#modelling", "href": "07-multilevel-01.html#modelling", "title": "7  Multilevel Modelling - Part 1", - "section": "7.3 Modelling", - "text": "7.3 Modelling\nWe should now be persuaded that ignoring the hierarchical structure of data may be a major issue. Let us now use a simple example to understand the intuition of multilevel model using the census data. We will seek to understand the spatial distribution of the proportion of population in unemployment in Liverpool, particularly why and where concentrations in this proportion occur. To illustrate the advantages of taking a multilevel modelling approach, we will start by estimating a linear regression model and progressively building complexity. We will first estimate a model and then explain the intuition underpinning the process. We will seek to gain a general understanding of multilevel modelling. If you are interested in the statistical and mathemathical formalisation of the underpinning concepts, please refer to Gelman and Hill (2006).\nWe first need to want to understand our dependent variable: its density ditribution;\n\nggplot(data = oa_shp) +\ngeom_density(alpha=0.8, colour=\"black\", fill=\"lightblue\", aes(x = unemp)) +\n theme_classic()\n\n\n\n\n\nsummary(unemp)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n0.00000 0.05797 0.10256 0.11581 0.16129 0.50000 \n\n\nand, its spatial distribution:\n\n# ensure geometry is valid\noa_shp = sf::st_make_valid(oa_shp)\n\n# create a map\nlegend_title = expression(\"% unemployment\")\nmap_oa = tm_shape(oa_shp) +\n tm_fill(col = \"unemp\", title = legend_title, palette = magma(256, begin = 0.25, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_oa\n\n\n\n\nLet us look at those areas:\n\n# high %s\noa_shp %>% filter(unemp > 0.2) %>% \n dplyr::select(oa_cd, pop, unemp) \n\nSimple feature collection with 203 features and 3 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 333993.8 ymin: 379748.5 xmax: 345600.2 ymax: 397681.5\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n oa_cd pop unemp geometry\n1 E00032992 240 0.2519685 POLYGON ((335804.9 389421.6...\n2 E00033008 345 0.2636364 POLYGON ((335080 388528, 33...\n3 E00033074 299 0.2075472 POLYGON ((336947.3 387766.7...\n4 E00033075 254 0.2288136 POLYGON ((336753.6 387465.2...\n5 E00033080 197 0.2647059 POLYGON ((338196 387079, 33...\n6 E00033103 298 0.2148148 POLYGON ((340484 385429.6, ...\n7 E00033116 190 0.2156863 POLYGON ((341960.7 386422.1...\n8 E00033134 190 0.2674419 POLYGON ((337137 393089.6, ...\n9 E00033137 289 0.2661290 POLYGON ((337363.8 392122.4...\n10 E00033138 171 0.3561644 POLYGON ((337481.5 392166.2...\n\n\n\n7.3.1 Baseline Linear Regression Model\nNow let us estimate a simple linear regression model with the intercept only:\n\n# specify a model equation\neq1 <- unemp ~ 1\nmodel1 <- lm(formula = eq1, data = oa_shp)\n\n# estimates\nsummary(model1)\n\n\nCall:\nlm(formula = eq1, data = oa_shp)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.11581 -0.05784 -0.01325 0.04548 0.38419 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.115812 0.001836 63.09 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.07306 on 1583 degrees of freedom\n\n\nTo understand the differences between the linear regression model and multilevel models, let us consider the model we have estimated:\n\\[y_{i} = \\beta_{0} + e_{i}\\] where \\(y_{i}\\) represents the proportion of the unemployed resident population in the OA \\(i\\); \\(\\beta_{0}\\) is the regression intercept and measures the average proportion of the unemployed resident population across OAs; and, \\(e_{i}\\) is the error term. But how do we deal with the hierarchical structure of the data?\n\n7.3.1.1 Limitations\nBefore looking at the answer, let’s first understand some of the key limitations of the linear regression model to handle the hierarchical structure of data. A key limitation of the linear regression model is that it only captures average relationships in the data. It does not capture variations in the relationship between variables across areas or groups. Another key limitation is that the linear regression model can capture associations at either macro or micro levels, but it does not simultaneously measure their interdependencies.\nTo illustrate this, let us consider the regression intercept. It indicates that the average percentage of unemployed population at the OA level is 0.12 but this model ignores any spatial clustering ie. the percentage of unemployed population tends to be similar across OAs nested within a same LSOA or MSOA. A side effect of ignoring this is that our standard errors are biased, and thus claims about statistical significance based on them would be misleading. Additionally, this situation also means we cannot explore variations in the percentage of unemployed population across LSOAs or MSOAs ie. how the percentage of unemployed population may be dependent on various contextual factors at these geographical scales.\n\n\n7.3.1.2 Fixed Effect Approach\nAn alternative approach is to adopt a fixed effects approach, or no-pooling model; that is, adding dummy variables indicating the group classification into the regression model eg. the way OAs is nested within LSOAs (or MSOAs). This approach has limitations. First, there is high risk of overfitting. The number of groups may be too large, relative to the number of observations. Second, the estimation of multiple parameters may be required so that measuring differences between groups may be challenging. Third, a fixed effects approach does not allow including group-level explanatory variables. You can try fitting a linear regression model extending our estimated model to include dummy variables for individual LSOAs (and/or MSOAs) so you can compare this to the multilevel model below.\nAn alternative is fitting separate linear regression models for each group. This approach is not always possible if there are groups with small sizes." + "section": "\n7.3 Modelling", + "text": "7.3 Modelling\nWe should now be persuaded that ignoring the hierarchical structure of data may be a major issue. Let us now use a simple example to understand the intuition of multilevel model using the census data. We will seek to understand the spatial distribution of the proportion of population in unemployment in Liverpool, particularly why and where concentrations in this proportion occur. To illustrate the advantages of taking a multilevel modelling approach, we will start by estimating a linear regression model and progressively building complexity. We will first estimate a model and then explain the intuition underpinning the process. We will seek to gain a general understanding of multilevel modelling. If you are interested in the statistical and mathemathical formalisation of the underpinning concepts, please refer to Gelman and Hill (2006).\nWe first need to want to understand our dependent variable: its density ditribution;\n\nggplot(data = oa_shp) +\n geom_density(alpha=0.8, colour=\"black\", fill=\"lightblue\", aes(x = unemp)) +\n theme_plot_tufte()\n\n\n\n\n\n\n\n\nsummary(unemp)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n0.00000 0.05797 0.10256 0.11581 0.16129 0.50000 \n\n\nand, its spatial distribution:\n\n# ensure geometry is valid\noa_shp = sf::st_make_valid(oa_shp)\n\n# create a map\nlegend_title = expression(\"% unemployment\")\nmap_oa = tm_shape(oa_shp) +\n tm_fill(col = \"unemp\", title = legend_title, palette = magma(256, begin = 0.25, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_oa\n\n\n\n\n\n\n\nLet us look at those areas:\n\n# high %s\noa_shp %>% filter(unemp > 0.2) %>% \n dplyr::select(oa_cd, pop, unemp) \n\nSimple feature collection with 203 features and 3 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 333993.8 ymin: 379748.5 xmax: 345600.2 ymax: 397681.5\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n oa_cd pop unemp geometry\n1 E00032992 240 0.2519685 POLYGON ((335804.9 389421.6...\n2 E00033008 345 0.2636364 POLYGON ((335080 388528, 33...\n3 E00033074 299 0.2075472 POLYGON ((336947.3 387766.7...\n4 E00033075 254 0.2288136 POLYGON ((336753.6 387465.2...\n5 E00033080 197 0.2647059 POLYGON ((338196 387079, 33...\n6 E00033103 298 0.2148148 POLYGON ((340484 385429.6, ...\n7 E00033116 190 0.2156863 POLYGON ((341960.7 386422.1...\n8 E00033134 190 0.2674419 POLYGON ((337137 393089.6, ...\n9 E00033137 289 0.2661290 POLYGON ((337363.8 392122.4...\n10 E00033138 171 0.3561644 POLYGON ((337481.5 392166.2...\n\n\n\n7.3.1 Baseline Linear Regression Model\nNow let us estimate a simple linear regression model with the intercept only:\n\n# specify a model equation\neq1 <- unemp ~ 1\nmodel1 <- lm(formula = eq1, data = oa_shp)\n\n# estimates\nsummary(model1)\n\n\nCall:\nlm(formula = eq1, data = oa_shp)\n\nResiduals:\n Min 1Q Median 3Q Max \n-0.11581 -0.05784 -0.01325 0.04548 0.38419 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 0.115812 0.001836 63.09 <2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 0.07306 on 1583 degrees of freedom\n\n\nTo understand the differences between the linear regression model and multilevel models, let us consider the model we have estimated:\n\\[y_{i} = \\beta_{0} + e_{i}\\] where \\(y_{i}\\) represents the proportion of the unemployed resident population in the OA \\(i\\); \\(\\beta_{0}\\) is the regression intercept and measures the average proportion of the unemployed resident population across OAs; and, \\(e_{i}\\) is the error term. But how do we deal with the hierarchical structure of the data?\n\n7.3.1.1 Limitations\nBefore looking at the answer, let’s first understand some of the key limitations of the linear regression model to handle the hierarchical structure of data. A key limitation of the linear regression model is that it only captures average relationships in the data. It does not capture variations in the relationship between variables across areas or groups. Another key limitation is that the linear regression model can capture associations at either macro or micro levels, but it does not simultaneously measure their interdependencies.\nTo illustrate this, let us consider the regression intercept. It indicates that the average percentage of unemployed population at the OA level is 0.12 but this model ignores any spatial clustering ie. the percentage of unemployed population tends to be similar across OAs nested within a same LSOA or MSOA. A side effect of ignoring this is that our standard errors are biased, and thus claims about statistical significance based on them would be misleading. Additionally, this situation also means we cannot explore variations in the percentage of unemployed population across LSOAs or MSOAs ie. how the percentage of unemployed population may be dependent on various contextual factors at these geographical scales.\n\n7.3.1.2 Fixed Effect Approach\nAn alternative approach is to adopt a fixed effects approach, or no-pooling model; that is, adding dummy variables indicating the group classification into the regression model eg. the way OAs is nested within LSOAs (or MSOAs). This approach has limitations. First, there is high risk of overfitting. The number of groups may be too large, relative to the number of observations. Second, the estimation of multiple parameters may be required so that measuring differences between groups may be challenging. Third, a fixed effects approach does not allow including group-level explanatory variables. You can try fitting a linear regression model extending our estimated model to include dummy variables for individual LSOAs (and/or MSOAs) so you can compare this to the multilevel model below.\nAn alternative is fitting separate linear regression models for each group. This approach is not always possible if there are groups with small sizes.", + "crumbs": [ + "7  Multilevel Modelling - Part 1" + ] }, { "objectID": "07-multilevel-01.html#multilevel-modelling-random-intercept-model", "href": "07-multilevel-01.html#multilevel-modelling-random-intercept-model", "title": "7  Multilevel Modelling - Part 1", - "section": "7.4 Multilevel Modelling: Random Intercept Model", - "text": "7.4 Multilevel Modelling: Random Intercept Model\nWe use multilevel modelling to account for the hierarchical nature of the data by explicitly recognising that OAs are nested within LSOAs and MSOAs. Multilevel models can easily be estimated using in R using the package lme4. We implement an two-level model to allow for variation across LSOAs. We estimate an only intercept model allowing for variation across LSOAs. In essence, we are estimating a model with varying intercept coefficient by LSOA. As you can see in the code chunk below, the equation has an additional component. This is the group component or LSOA effect. The (1 | lsoa_cd) means that we are allowing the intercept, represented by 1, to vary by LSOA.\n\n# specify a model equation\neq2 <- unemp ~ 1 + (1 | lsoa_cd)\nmodel2 <- lmer(eq2, data = oa_shp)\n\n# estimates\nsummary(model2)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | lsoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4382.6\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.8741 -0.5531 -0.1215 0.4055 5.8207 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd (Intercept) 0.002701 0.05197 \n Residual 0.002575 0.05074 \nNumber of obs: 1584, groups: lsoa_cd, 298\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.114316 0.003277 34.89\n\n\nWe can estimate a three-level model by adding (1 | msoa_cd) to allow the intercept to also vary by MSOAs and account for the nesting structure of LSOAs within MSOAs.\n\n# specify a model equation\neq3 <- unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)\nmodel3 <- lmer(eq3, data = oa_shp)\n\n# estimates\nsummary(model3)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4529.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.5624 -0.5728 -0.1029 0.4228 6.1363 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd (Intercept) 0.0007603 0.02757 \n msoa_cd (Intercept) 0.0020735 0.04554 \n Residual 0.0025723 0.05072 \nNumber of obs: 1584, groups: lsoa_cd, 298; msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.115288 0.006187 18.64\n\n\nWe see two sets of coefficients: fixed effects and random effects. Fixed effects correspond to the standard linear regression coefficients. Their interpretation is as usual. Random effects are the novelty. It is a term in multilevel modelling and refers to varying coefficients i.e. the randomness in the probability of the model for the group-level coefficients. Specifically they relate to estimates of the average variance and standard deviation within groups (i.e. LSOAs or MSOAs). Intiutively, variance and standard deviation indicate the extent to which the intercept, on average, varies by LSOAs and MSOAs.\n\n\n\nFig. 2. Variation of observations around their level 1 group mean.\n\n\n\n\n\nFig. 3. Variation of level 1 group mean around their level 2 group mean.\n\n\n\n\n\nFig. 4. Grand mean.\n\n\nMore formally, we first estimated the simplest regression model which is an intercept-only model and equivalent to the sample mean (i.e. the fixed part of the model):\n\\[y_{ijk} = \\mu + e_{ijk}\\] and then we made the random part of the model (\\(e_{ijk}\\)) more complex to account for the hierarchical structure of the data by estimating the following three-level regression model:\n\\[y_{ijk} = \\mu + u_{i..} + u_{ij.} + e_{ijk}\\]\nwhere \\(y_{ijk}\\) represents the proportion of unemployed population in OA \\(i\\) nested within LSOA \\(j\\) and MSOA \\(k\\); \\(\\mu\\) represents the sample mean and the fixed part of the model; \\(e_{ijk}\\) is the deviation of an observation from its LSOA mean; \\(u_{ij.}\\) is the deviation of the LSOA mean from its MSOA mean; \\(u_{i..}\\) is the deviation of the MSOA mean from the fixed part of the model \\(\\mu\\). Conceptually, this model is decomposing the variance of the model in terms of the hierarchical structure of the data. It is partitioning the observation’s residual into three parts or variance components. These components measure the relative extent of variation of each hierarchical level ie. LSOA, MSOA and grand means. To estimate the set of residuals, they are assumed to follow a normal distribution and are obtained after fitting the model and are based on the estimates of the model parameters (i.e. intercept and variances of the random parameters).\nLet’s now return to our three-level model (reported again below), we see that the intercept or fixed part of the model is the same as for the linear regression. The multilevel model reports greater standard errors. Multilevel models capture the hierarchical structure of the data and thus more precisely estimate the standard errors for our parameters.\n\n# report model 3\nsummary(model3)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4529.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.5624 -0.5728 -0.1029 0.4228 6.1363 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd (Intercept) 0.0007603 0.02757 \n msoa_cd (Intercept) 0.0020735 0.04554 \n Residual 0.0025723 0.05072 \nNumber of obs: 1584, groups: lsoa_cd, 298; msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.115288 0.006187 18.64\n\n\n\n7.4.1 Interpretation\n\nFixed effects\n\nWe start by examining the fixed effects or estimated model averaging over LSOAs and MSOAs, \\(y_{ijk} = 0.115288\\) which can also be called by executing:\n\nfixef(model3)\n\n(Intercept) \n 0.1152881 \n\n\nTh estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as 0.115288 and is statistically significant at 5% significance.\n\nRandom effects\n\nThe set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The lsoa_cd, msoa_cd and Residual estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is 0.0007603, 0.0020735 and 0.0025723, respectively.\n\n\n7.4.2 Variance Partition Coefficient (VPC)\nThe purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.2 For our case, the VPC at the LSOA level indicates that 14% of the variation in percentage of unemployed resident population across OAs can be explained by differences across LSOAs. What is the VPC at the MSOA level?\n\nvpc_lsoa <- 0.0007603 / (0.0007603 + 0.0020735 + 0.0025723)\nvpc_lsoa * 100\n\n[1] 14.06374\n\n\nYou can also obtain the VPC by executing:\n\nsumm(model3)\n\n\n\n\n \n Observations \n 1584 \n \n \n Dependent variable \n unemp \n \n \n Type \n Mixed effects linear regression \n \n\n \n\n \n AIC \n -4521.26 \n \n \n BIC \n -4499.79 \n \n \n Pseudo-R² (fixed effects) \n 0.00 \n \n \n Pseudo-R² (total) \n 0.52 \n \n\n \n \nFixed Effects\n \n \n Est. \n S.E. \n t val. \n d.f. \n p \n \n \n\n \n (Intercept) \n 0.12 \n 0.01 \n 18.63 \n 59.98 \n 0.00 \n \n\n\n p values calculated using Kenward-Roger standard errors and d.f. \n \n \nRandom Effects\n \n Group \n Parameter \n Std. Dev. \n \n \n\n \n lsoa_cd \n (Intercept) \n 0.03 \n \n \n msoa_cd \n (Intercept) \n 0.05 \n \n \n Residual \n \n 0.05 \n \n\n \n \nGrouping Variables\n \n Group \n # groups \n ICC \n \n \n\n \n lsoa_cd \n 298 \n 0.14 \n \n \n msoa_cd \n 61 \n 0.38 \n \n\n\n\n\n\n\n7.4.3 Uncertainty of Estimates\nYou may have noticed that lme4 does not provide p-values, because of various reasons as explained by Doug Bates, one of the author of lme4. These explanations mainly refer to the complexity of dealing with varying sample sizes at a given hierarchical level. The number of observations at each hierarchical level varies across individual groupings (i.e. LSOA or MSOA). It may even be one single observation. This has implications for the distributional assumptions, denominator degrees of freedom and how to approximate a “best” solution. Various approaches exist to compute the statistical significance of estimates. We use the confint function available within lme4 to obtain confidence intervals.\n\nconfint(model3, level = 0.95)\n\nComputing profile confidence intervals ...\n\n\n 2.5 % 97.5 %\n.sig01 0.02360251 0.03189046\n.sig02 0.03707707 0.05562307\n.sigma 0.04882281 0.05273830\n(Intercept) 0.10307341 0.12751103\n\n\n.sig01 refers to the LSOA level; .sig02 refers to the MSOA level; and, .sigma refers to the OA level.\n\n\n7.4.4 Assessing Group-level Variation\nEstimated regression coefficients\nIn multilevel modelling, our primary interest is in knowing differences across groups. To visualise the estimated model within each group (ie. LSOA and MSOA), we type:\n\ncoef_m3 <- coef(model3)\nhead(coef_m3$lsoa_cd,5)\n\n (Intercept)\nE01006512 0.09915456\nE01006513 0.09889615\nE01006514 0.09297051\nE01006515 0.09803754\nE01006518 0.09642939\n\n\nThe results indicate that the estimated regression line is \\(y = 0.09915456\\) for LSOA E01006512; \\(y = 0.09889615\\) for LSOA E01006513 and so forth. Try getting the estimated model within each MSOA.\nRandom effects\nWe can look at the estimated group-level (or LSOA-level and MSOA-level) errors; that is, random effects:\n\nranef_m3 <- ranef(model3)\nhead(ranef_m3$lsoa_cd, 5)\n\n (Intercept)\nE01006512 -0.01613353\nE01006513 -0.01639194\nE01006514 -0.02231758\nE01006515 -0.01725055\nE01006518 -0.01885870\n\n\nGroup-level errors indicate how much the intercept is shifted up or down in particular groups (ie. LSOAs or MSOAs). Thus, for example, in LSOA E01006512, the estimated intercept is -0.01613353 lower than average, so that the regression line is (0.1152881 - 0.01613353) = 0.09915457 which is what we observed from the call to coef().\nWe can also obtain group-level errors (random effects) by using a simulation approach, labelled “Empirical Bayes” and discussed here. To this end, we run:\n\n# obtain estimates\nmerTools::REsim(model3) %>% head(10)\n\n groupFctr groupID term mean median sd\n1 lsoa_cd E01006512 (Intercept) -0.016090933 -0.016279825 0.020134603\n2 lsoa_cd E01006513 (Intercept) -0.015635892 -0.016193114 0.019647670\n3 lsoa_cd E01006514 (Intercept) -0.022289829 -0.022374465 0.018657688\n4 lsoa_cd E01006515 (Intercept) -0.018205227 -0.017420404 0.018735274\n5 lsoa_cd E01006518 (Intercept) -0.017721752 -0.016370427 0.019606654\n6 lsoa_cd E01006519 (Intercept) -0.015710212 -0.015621840 0.009397891\n7 lsoa_cd E01006520 (Intercept) -0.023901618 -0.023790192 0.018551656\n8 lsoa_cd E01006521 (Intercept) 0.007266539 0.006503922 0.019235318\n9 lsoa_cd E01006522 (Intercept) 0.019576849 0.018556373 0.019585302\n10 lsoa_cd E01006523 (Intercept) 0.004543610 0.002947114 0.019247207\n\n\nThe results contain the estimated mean, median and standard deviation for the intercept within each group (e.g. LSOA). The mean estimates are similar to those obtained from ranef with some small differences due to rounding.\nTo gain an undertanding of the general pattern of the random effects, we can use caterpillar plots via plotREsim - reported below. The plot on the right shows the estimated random effects for each MSOA and their respective interval estimate. Note that random effects are on average zero, represented by the red horizontal line. Intervals that do not include zero are in bold. Also note that the width of the confidence interval depends on the standard error of the respective residual estimate, which is inversely related to the size of the sample. The residuals represent an observation departures from the grand mean, so an observation whose confidence interval does not overlap the line at zero (representing the mean proportion of unemployed population across all areas) is said to differ significantly from the average at the 5% level.\n\n# plot\nplotREsim(REsim(model3)) \n\n\n\n\nFocusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton. What districts do we have at the other extreme?\n\nre <- REsim(model3)\noa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp) %>%\n filter(as.character(msoa_cd) == \"E02001387\" | as.character(msoa_cd) == \"E02001393\")\n\nSimple feature collection with 49 features and 3 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 339178.6 ymin: 386244.2 xmax: 341959.9 ymax: 389646.7\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n msoa_cd ward_nm unemp geometry\n1 E02001393 Allerton and Hunts Cross 0.03246753 POLYGON ((341333.6 387163.2...\n2 E02001393 Allerton and Hunts Cross 0.03684211 POLYGON ((340658.2 387205.6...\n3 E02001393 Church 0.04098361 POLYGON ((339908.1 387222.3...\n4 E02001393 Allerton and Hunts Cross 0.05982906 POLYGON ((340306 386587, 34...\n5 E02001393 Church 0.01212121 POLYGON ((339974.2 387118.5...\n6 E02001393 Church 0.09219858 POLYGON ((340181.4 386957.8...\n7 E02001393 Church 0.01986755 POLYGON ((340301.2 386582.2...\n8 E02001393 Church 0.04615385 POLYGON ((340375.9 386918.6...\n9 E02001393 Allerton and Hunts Cross 0.04117647 POLYGON ((340435.3 386337.4...\n10 E02001393 Allerton and Hunts Cross 0.02272727 POLYGON ((340681.7 386614.4...\n\n\nWe can also map the MSOA-level random effects. To this end, we first need to read a shapefile containing data at the MSOA level and merge it with the random effects estimates.\n\n# read data\nmsoa_shp <- st_read(\"data/mlm/MSOA.shp\")\n\nReading layer `MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/mlm/MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 17 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# create a dataframe for MSOA-level random effects\nre_msoa <- re %>% filter(groupFctr == \"msoa_cd\")\nstr(re_msoa)\n\n'data.frame': 61 obs. of 6 variables:\n $ groupFctr: chr \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" ...\n $ groupID : chr \"E02001347\" \"E02001348\" \"E02001349\" \"E02001350\" ...\n $ term : chr \"(Intercept)\" \"(Intercept)\" \"(Intercept)\" \"(Intercept)\" ...\n $ mean : num -0.01306 -0.02417 -0.03594 0.00455 0.02435 ...\n $ median : num -0.01421 -0.02405 -0.03499 0.00161 0.02427 ...\n $ sd : num 0.0356 0.0308 0.032 0.0306 0.0152 ...\n\n# merge data\nmsoa_shp <- merge(x = msoa_shp, y = re_msoa, by.x = \"MSOA_CD\", by.y = \"groupID\")\n\nNow we can create our map:\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nlegend_title = expression(\"MSOA-level residuals\")\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"mean\", title = legend_title, palette = magma(256, begin = 0, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_msoa\n\n\n\n\n\n\n7.4.5 Adding Individual-level Predictors\nIn this example, \\(\\mu\\) represents the sample mean but it could include a collection of independent variables or predictors. To explain the logic, we will assume that unemployment is strongly associated to long-term illness. We could expect that long-term illness (lt_ill) will reduce the chances of working and therefore being unemployed. Note that our focus is on the relationship, not on establishing causation. Specifically we want to estimate the relationship between unemployment and long-term illness and we are interested in variations in OA-level unemployment by MSOAs so we will estimate the following two-level model:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1}x_{ij} + e_{ij}\\] MSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + u_{0j}\\] Replacing the first equation into the second, we have:\n\\[y_{ij} = (\\beta_{0} + u_{0j}) + \\beta_{1}x_{ij} + e_{ij}\\] where \\(y\\) the proportion of unemployed population in OA \\(i\\) within MSOA \\(j\\); \\(\\beta_{0}\\) is the fixed intercept (averaging over all MSOAs); \\(u_{0j}\\) represents the MSOA-level residuals or random effects; \\(\\beta_{0}\\) and \\(u_{0j}\\) together represent the varying-intercept; \\(\\beta_{1}\\) is the slope coefficient; \\(x_{ij}\\) represents the percentage of long-term illness population; and, \\(e_{ij}\\) is the individual-level residuals.\nWe estimate the model executing:\n\n# change to proportion\noa_shp$lt_ill <- lt_ill/100\n\n# specify a model equation\neq4 <- unemp ~ lt_ill + (1 | msoa_cd)\nmodel4 <- lmer(eq4, data = oa_shp)\n\n# estimates\nsummary(model4)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4711.9\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-5.1941 -0.5718 -0.0906 0.4507 5.9393 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n msoa_cd (Intercept) 0.001421 0.03769 \n Residual 0.002674 0.05171 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.04682 0.00625 7.492\nlt_ill 0.29588 0.01615 18.317\n\nCorrelation of Fixed Effects:\n (Intr)\nlt_ill -0.600\n\n\nFixed effects: model averaging over MSOAs\n\nfixef(model4)\n\n(Intercept) lt_ill \n 0.04681959 0.29588110 \n\n\nyields an estimated regression line in an average McSOA: \\(y = 0.04681959 + 0.29588110x\\)\nRandom effects: MSOA-level errors\n\nranef_m4 <- ranef(model4)\nhead(ranef_m4$msoa_cd, 5)\n\n (Intercept)\nE02001347 -0.017474815\nE02001348 -0.021203807\nE02001349 -0.022469313\nE02001350 -0.003539869\nE02001351 0.008502813\n\n\nyields an estimated intercept for MSOA E02001347 which is 0.017474815 lower than the average with a regression line: (0.04681959 - 0.017474815) + 0.29588110x = 0.02934478 + 0.29588110x. You can confirm this by looking at the estimated model within each MSOA by executing (remove the # sign):\n\n#coef(model4)\n\nFixed effect correlations\nIn the bottom of the output, we have the correlations between the fixed-effects estimates. In our example, it refers to the correlation between \\(\\beta_{0}\\) and \\(\\beta_{1}\\). It is negative indicating that in MSOAs where the relationship between unemployment and long-term illness is greater, as measured by \\(\\beta_{1}\\), the average proportion of unemployed people tends to be smaller, as captured by \\(\\beta_{0}\\).\n\n\n7.4.6 Adding Group-level Predictors\nWe can also add group-level predictors. We use the formulation:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1}x_{ij} + e_{ij}\\]\nMSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + \\gamma_{1}m_{j} + u_{0j}\\]\nwhere \\(x_{ij}\\) is the OA-level proportion of population suffering long-term illness and \\(m_{j}\\) is the MSOA-level proportion of male population. We first need to create this group-level predictor:\n\n# detach OA shp and attach MSOA shp\ndetach(oa_shp)\nattach(msoa_shp)\n\nThe following object is masked from package:viridis:\n\n unemp\n\n# group-level predictor\nmsoa_shp$pr_male <- males/pop\n\n# remove geometries\nmsoa_df <- `st_geometry<-`(msoa_shp, NULL)\n\n# select variables\nmsoa_df <- msoa_df %>% dplyr::select(MSOA_CD, pop, pr_male)\n\n# merge data sets\noa_shp <- merge(x=oa_shp, y=msoa_df, by.x = \"msoa_cd\", by.y=\"MSOA_CD\")\n\n# inspect data\nhead(oa_shp[1:10, c(\"msoa_cd\", \"oa_cd\", \"unemp\", \"pr_male\")])\n\nSimple feature collection with 6 features and 4 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 337693.5 ymin: 396068.2 xmax: 339430.9 ymax: 397790\nProjected CRS: Transverse_Mercator\n msoa_cd oa_cd unemp pr_male geometry\n1 E02001347 E00033730 0.10322581 0.4775905 POLYGON ((338376 397059, 33...\n2 E02001347 E00033722 0.06306306 0.4775905 POLYGON ((337929.4 397669.9...\n3 E02001347 E00033712 0.09090909 0.4775905 POLYGON ((338830 396068.2, ...\n4 E02001347 E00033739 0.09401709 0.4775905 POLYGON ((339140.3 397191, ...\n5 E02001347 E00033719 0.05855856 0.4775905 POLYGON ((338128.8 397658.6...\n6 E02001347 E00033711 0.12195122 0.4775905 POLYGON ((339163.2 396833.6...\n\n\nWe can now estimate our model:\n\ndetach(msoa_shp)\nattach(oa_shp)\n\nThe following object is masked from package:viridis:\n\n unemp\n\n# specify a model equation\neq5 <- unemp ~ lt_ill + pr_male + (1 | msoa_cd)\nmodel5 <- lmer(eq5, data = oa_shp)\n\n# estimates\nsummary(model5)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + pr_male + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4712.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-5.2162 -0.5696 -0.0929 0.4549 5.9370 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n msoa_cd (Intercept) 0.001391 0.03729 \n Residual 0.002674 0.05171 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) -0.07746 0.08768 -0.883\nlt_ill 0.29781 0.01620 18.389\npr_male 0.25059 0.17642 1.420\n\nCorrelation of Fixed Effects:\n (Intr) lt_ill\nlt_ill -0.118 \npr_male -0.997 0.075\n\n\nThis model includes the proportion of males and intercepts that vary by MSOA. The lmer() function only accepts predictors at the individual level, so we have included data on the proportion of male population at this level. Explore and interpret the model running the functions below:\n\n# fixed effects\nfixef(model5)\n\n(Intercept) lt_ill pr_male \n -0.0774607 0.2978084 0.2505913 \n\n\n\n# random effects\nranef_m5 <- ranef(model5)\nhead(ranef_m5$msoa_cd, 5)\n\n (Intercept)\nE02001347 -0.013625261\nE02001348 -0.019757846\nE02001349 -0.023709992\nE02001350 0.003003861\nE02001351 0.003508477\n\n\nAdding group-level predictors tends to improve inferences for group coefficients. Examine the confidence intervals, in order to evalute how the precision of our estimates of the MSOA intercepts have changed. Have confidence intervals for the intercepts of Model 4 and 5 increased or reduced? Hint: look at how to get the confidence intervals above." + "section": "\n7.4 Multilevel Modelling: Random Intercept Model", + "text": "7.4 Multilevel Modelling: Random Intercept Model\nWe use multilevel modelling to account for the hierarchical nature of the data by explicitly recognising that OAs are nested within LSOAs and MSOAs. Multilevel models can easily be estimated using in R using the package lme4. We implement an two-level model to allow for variation across LSOAs. We estimate an only intercept model allowing for variation across LSOAs. In essence, we are estimating a model with varying intercept coefficient by LSOA. As you can see in the code chunk below, the equation has an additional component. This is the group component or LSOA effect. The (1 | lsoa_cd) means that we are allowing the intercept, represented by 1, to vary by LSOA.\n\n# specify a model equation\neq2 <- unemp ~ 1 + (1 | lsoa_cd)\nmodel2 <- lmer(eq2, data = oa_shp)\n\n# estimates\nsummary(model2)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | lsoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4382.6\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.8741 -0.5531 -0.1215 0.4055 5.8207 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd (Intercept) 0.002701 0.05197 \n Residual 0.002575 0.05074 \nNumber of obs: 1584, groups: lsoa_cd, 298\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.114316 0.003277 34.89\n\n\nWe can estimate a three-level model by replacing (1 | lsoa_cd) for (1 | msoa_cd/lsoa_cd) to allow the intercept to also vary by MSOAs and account for the nesting structure of LSOAs within MSOAs. In multilevel modelling, these types of models are formally known as nested random effects and they differ from a different set of models known as crossed random effects.\n::: column-margin ::: callout-note A crossed random effect model in our example would be expressed as follows:\nunemp ~ 1 + (1 | lsoa_cd) + (1 | msoa_cd)\n::: ::: column-margin\n\n# specify a model equation\neq3 <- unemp ~ 1 + (1 | msoa_cd/lsoa_cd)\nmodel3 <- lmer(eq3, data = oa_shp)\n\n# estimates\nsummary(model3)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | msoa_cd/lsoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4529.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.5624 -0.5728 -0.1029 0.4228 6.1363 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd:msoa_cd (Intercept) 0.0007603 0.02757 \n msoa_cd (Intercept) 0.0020735 0.04554 \n Residual 0.0025723 0.05072 \nNumber of obs: 1584, groups: lsoa_cd:msoa_cd, 298; msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.115288 0.006187 18.64\n\n\nWe see two sets of coefficients: fixed effects and random effects. Fixed effects correspond to the standard linear regression coefficients. Their interpretation is as usual. Random effects are the novelty. It is a term in multilevel modelling and refers to varying coefficients i.e. the randomness in the probability of the model for the group-level coefficients. Specifically they relate to estimates of the average variance and standard deviation within groups (i.e. LSOAs or MSOAs). Intiutively, variance and standard deviation indicate the extent to which the intercept, on average, varies by LSOAs and MSOAs.\n\n\nFig. 2. Variation of observations around their level 1 group mean.\n\n\n\nFig. 3. Variation of level 1 group mean around their level 2 group mean.\n\n\n\nFig. 4. Grand mean.\n\nMore formally, we first estimated the simplest regression model which is an intercept-only model and equivalent to the sample mean (i.e. the fixed part of the model):\n\\[y_{ijk} = \\mu + e_{ijk}\\] and then we made the random part of the model (\\(e_{ijk}\\)) more complex to account for the hierarchical structure of the data by estimating the following three-level regression model:\n\\[y_{ijk} = \\mu + u_{i..} + u_{ij.} + e_{ijk}\\]\nwhere \\(y_{ijk}\\) represents the proportion of unemployed population in OA \\(i\\) nested within LSOA \\(j\\) and MSOA \\(k\\); \\(\\mu\\) represents the sample mean and the fixed part of the model; \\(e_{ijk}\\) is the deviation of an observation from its LSOA mean; \\(u_{ij.}\\) is the deviation of the LSOA mean from its MSOA mean; \\(u_{i..}\\) is the deviation of the MSOA mean from the fixed part of the model \\(\\mu\\). Conceptually, this model is decomposing the variance of the model in terms of the hierarchical structure of the data. It is partitioning the observation’s residual into three parts or variance components. These components measure the relative extent of variation of each hierarchical level ie. LSOA, MSOA and grand means. To estimate the set of residuals, they are assumed to follow a normal distribution and are obtained after fitting the model and are based on the estimates of the model parameters (i.e. intercept and variances of the random parameters).\nLet’s now return to our three-level model (reported again below), we see that the intercept or fixed part of the model is the same as for the linear regression. The multilevel model reports greater standard errors. Multilevel models capture the hierarchical structure of the data and thus more precisely estimate the standard errors for our parameters.\n\n# report model 3\nsummary(model3)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ 1 + (1 | msoa_cd/lsoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4529.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-2.5624 -0.5728 -0.1029 0.4228 6.1363 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n lsoa_cd:msoa_cd (Intercept) 0.0007603 0.02757 \n msoa_cd (Intercept) 0.0020735 0.04554 \n Residual 0.0025723 0.05072 \nNumber of obs: 1584, groups: lsoa_cd:msoa_cd, 298; msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.115288 0.006187 18.64\n\n\n\n7.4.1 Interpretation\n\nFixed effects\n\nWe start by examining the fixed effects or estimated model averaging over LSOAs and MSOAs, \\(y_{ijk} = 0.115288\\) which can also be called by executing:\n\nfixef(model3)\n\n(Intercept) \n 0.1152881 \n\n\nTh estimated intercept indicates that the overall mean taken across LSOAs and MSOAs is estimated as 0.1152881.\n\nRandom effects\n\nThe set of random effects contains three estimates of variance and standard deviation and refer to the variance components discussed above. The lsoa_cd:msoa_cd, msoa_cd and Residual estimates indicate that the extent of estimated LSOA-, MSOA- and individual-level variance is 0.0007603, 0.0020735 and 0.0025723, respectively.\n\n7.4.2 Variance Partition Coefficient (VPC)\nThe purpose of multilevel models is to partition variance in the outcome between the different groupings in the data. We thus often want to know the percentage of variation in the dependent variable accounted by differences across groups i.e. what proportion of the total variance is attributable to variation within-groups, or how much is found between-groups. The statistic to obtain this is termed the variance partition coefficient (VPC), or intraclass correlation.2 For our case, the VPC at the MSOA level indicates that 38% of the variation in percentage of unemployed resident population across OAs can be explained by differences across MSOAs.\n2 The VPC is equal to the intra-class correlation coefficient which is the correlation between the observations of the dependent variable selected randomly from the same group. For instance, if the VPC is 0.1, we would say that 10% of the variation is between groups and 90% within. The correlation between randomly chosen pairs of observations belonging to the same group is 0.1.\n\n\n\n\n\n\nTask What is the VPC at the LSOA level?\n\n\n\n\n\nvpc_msoa <- 0.0020735 / (0.0007603 + 0.0020735 + 0.0025723)\nvpc_msoa * 100\n\n[1] 38.35482\n\n\nYou can also obtain the VPC by executing:\n\nsumm(model3)\n\n\n\n\nObservations\n1584\n\n\nDependent variable\nunemp\n\n\nType\nMixed effects linear regression\n\n\n\n\n\n\nAIC\n-4521.26\n\n\nBIC\n-4499.79\n\n\nPseudo-R² (fixed effects)\n0.00\n\n\nPseudo-R² (total)\n0.52\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nFixed Effects\n\n\n\n\nEst.\nS.E.\nt val.\nd.f.\np\n\n\n\n(Intercept)\n0.12\n0.01\n18.63\n59.98\n0.00\n\n\n\n p values calculated using Kenward-Roger standard errors and d.f.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nRandom Effects\n\n\n\nGroup\nParameter\nStd. Dev.\n\n\n\n\nlsoa_cd:msoa_cd\n(Intercept)\n0.03\n\n\nmsoa_cd\n(Intercept)\n0.05\n\n\nResidual\n\n0.05\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nGrouping Variables\n\n\n\nGroup\n# groups\nICC\n\n\n\n\nlsoa_cd:msoa_cd\n298\n0.14\n\n\nmsoa_cd\n61\n0.38\n\n\n\n\n\n\n\n7.4.3 Uncertainty of Estimates\nYou may have noticed that lme4 does not provide p-values, because of various reasons as explained by Doug Bates, one of the author of lme4. These explanations mainly refer to the complexity of dealing with varying sample sizes at a given hierarchical level. The number of observations at each hierarchical level varies across individual groupings (i.e. LSOA or MSOA). It may even be one single observation. This has implications for the distributional assumptions, denominator degrees of freedom and how to approximate a “best” solution. Various approaches exist to compute the statistical significance of estimates. We use the confint function available within lme4 to obtain confidence intervals.\n\nconfint(model3, level = 0.95)\n\nComputing profile confidence intervals ...\n\n\n 2.5 % 97.5 %\n.sig01 0.02360251 0.03189046\n.sig02 0.03707707 0.05562307\n.sigma 0.04882281 0.05273830\n(Intercept) 0.10307341 0.12751103\n\n\n.sig01 refers to the LSOA level; .sig02 refers to the MSOA level; and, .sigma refers to the OA level.\n\n7.4.4 Assessing Group-level Variation\nEstimated regression coefficients\nIn multilevel modelling, our primary interest is in knowing differences across groups. To visualise the estimated model within each group (ie. LSOA and MSOA), we type:\n\ncoef_m3 <- coef(model3)\nhead(coef_m3$lsoa_cd,5)\n\n (Intercept)\nE01006512:E02001377 0.09915456\nE01006513:E02006932 0.09889615\nE01006514:E02001383 0.09297051\nE01006515:E02001383 0.09803754\nE01006518:E02001390 0.09642939\n\n\nThe results indicate that the estimated regression line is \\(y = 0.09915456\\) for LSOA E01006512 within MSOA E02001377; \\(y = 0.09889615\\) for LSOA E01006513 within MSOA E02006932 and so forth.\n\n\n\n\n\n\n\n\nTask Try getting the estimated model within each MSOA.\n\n\n\nRandom effects\nWe can look at the estimated group-level (or LSOA-level and MSOA-level) errors; that is, random effects:\n\nranef_m3 <- ranef(model3)\nhead(ranef_m3$lsoa_cd, 5)\n\n (Intercept)\nE01006512:E02001377 -0.01613353\nE01006513:E02006932 -0.01639194\nE01006514:E02001383 -0.02231758\nE01006515:E02001383 -0.01725055\nE01006518:E02001390 -0.01885870\n\n\nGroup-level errors indicate how much the intercept is shifted up or down in particular groups (ie. LSOAs or MSOAs). Thus, for example, in LSOA E01006512, the estimated intercept is -0.01613353 lower than average, so that the regression line is (0.1152881 - 0.01613353) = 0.09915457 which is what we observed from the call to coef().\nWe can also obtain group-level errors (random effects) by using a simulation approach, labelled “Empirical Bayes” and discussed here. To this end, we run:\n\n# obtain estimates\nmerTools::REsim(model3) %>% \n head(10)\n\n groupFctr groupID term mean median\n1 lsoa_cd:msoa_cd E01006512:E02001377 (Intercept) -0.017347076 -0.018066461\n2 lsoa_cd:msoa_cd E01006513:E02006932 (Intercept) -0.018159145 -0.019448281\n3 lsoa_cd:msoa_cd E01006514:E02001383 (Intercept) -0.024598753 -0.028318987\n4 lsoa_cd:msoa_cd E01006515:E02001383 (Intercept) -0.019774628 -0.021529676\n5 lsoa_cd:msoa_cd E01006518:E02001390 (Intercept) -0.019300622 -0.019234412\n6 lsoa_cd:msoa_cd E01006519:E02001402 (Intercept) -0.015437731 -0.015183404\n7 lsoa_cd:msoa_cd E01006520:E02001389 (Intercept) -0.024036792 -0.023435811\n8 lsoa_cd:msoa_cd E01006521:E02001398 (Intercept) 0.006797833 0.007696860\n9 lsoa_cd:msoa_cd E01006522:E02001394 (Intercept) 0.019526516 0.020374198\n10 lsoa_cd:msoa_cd E01006523:E02001398 (Intercept) 0.002772157 0.003187283\n sd\n1 0.01710357\n2 0.02214808\n3 0.02184431\n4 0.02027123\n5 0.02178862\n6 0.01030971\n7 0.02169687\n8 0.01897940\n9 0.02022446\n10 0.01896082\n\n\nThe results contain the estimated mean, median and standard deviation for the intercept within each group (e.g. LSOA). The mean estimates are similar to those obtained from ranef with some small differences due to rounding.\nTo gain an undertanding of the general pattern of the random effects, we can use caterpillar plots via plotREsim - reported below. The plot on the right shows the estimated random effects for each MSOA and their respective interval estimate. Note that random effects are on average zero, represented by the red horizontal line. Intervals that do not include zero are in bold. Also note that the width of the confidence interval depends on the standard error of the respective residual estimate, which is inversely related to the size of the sample. The residuals represent an observation departures from the grand mean, so an observation whose confidence interval does not overlap the line at zero (representing the mean proportion of unemployed population across all areas) is said to differ significantly from the average at the 5% level.\n\n# plot\nplotREsim(REsim(model3)) \n\n\n\n\n\n\n\nFocusing on the plot on the right, we see MSOAs whose mean proportion of unemployed population, assuming no explanatory variables, is lower than average. These are the dots below the horizontal red line. On the right-hand side of the plot, you will see MSOAs whose mean proportion is higher than average. The MSOAs with the smallest residuals include the districts of Allerton and Hunt Cross, Church, Childwall, Wavertree and Woolton.\n\n\n\n\n\n\n\n\nTask What districts do we have at the other extreme? Have a go at identifying them.\n\n\n\n\nre <- REsim(model3)\noa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp) %>%\n filter(as.character(msoa_cd) == \"E02001387\" | as.character(msoa_cd) == \"E02001393\")\n\nSimple feature collection with 49 features and 3 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 339178.6 ymin: 386244.2 xmax: 341959.9 ymax: 389646.7\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n msoa_cd ward_nm unemp geometry\n1 E02001393 Allerton and Hunts Cross 0.03246753 POLYGON ((341333.6 387163.2...\n2 E02001393 Allerton and Hunts Cross 0.03684211 POLYGON ((340658.2 387205.6...\n3 E02001393 Church 0.04098361 POLYGON ((339908.1 387222.3...\n4 E02001393 Allerton and Hunts Cross 0.05982906 POLYGON ((340306 386587, 34...\n5 E02001393 Church 0.01212121 POLYGON ((339974.2 387118.5...\n6 E02001393 Church 0.09219858 POLYGON ((340181.4 386957.8...\n7 E02001393 Church 0.01986755 POLYGON ((340301.2 386582.2...\n8 E02001393 Church 0.04615385 POLYGON ((340375.9 386918.6...\n9 E02001393 Allerton and Hunts Cross 0.04117647 POLYGON ((340435.3 386337.4...\n10 E02001393 Allerton and Hunts Cross 0.02272727 POLYGON ((340681.7 386614.4...\n\n\nWe can also map the MSOA-level random effects. To this end, we first need to read a shapefile containing data at the MSOA level and merge it with the random effects estimates.\n\n# read data\nmsoa_shp <- st_read(\"data/mlm/MSOA.shp\")\n\nReading layer `MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 17 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# create a dataframe for MSOA-level random effects\nre_msoa <- re %>% filter(groupFctr == \"msoa_cd\")\nstr(re_msoa)\n\n'data.frame': 61 obs. of 6 variables:\n $ groupFctr: chr \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" ...\n $ groupID : chr \"E02001347\" \"E02001348\" \"E02001349\" \"E02001350\" ...\n $ term : chr \"(Intercept)\" \"(Intercept)\" \"(Intercept)\" \"(Intercept)\" ...\n $ mean : num -0.01044 -0.02588 -0.0287 0.00515 0.02168 ...\n $ median : num -0.01136 -0.02427 -0.02754 0.00583 0.02144 ...\n $ sd : num 0.0323 0.0319 0.0332 0.0324 0.0186 ...\n\n# merge data\nmsoa_shp <- merge(x = msoa_shp, y = re_msoa, by.x = \"MSOA_CD\", by.y = \"groupID\")\n\nNow we can create our map:\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nlegend_title = expression(\"MSOA-level residuals\")\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"mean\", title = legend_title, palette = magma(256, begin = 0, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_msoa\n\n\n\n\n\n\n\n\n7.4.5 Adding Individual-level Predictors\nIn this example, \\(\\mu\\) represents the sample mean but it could include a collection of independent variables or predictors. To explain the logic, we will assume that unemployment is strongly associated to long-term illness. We could expect that long-term illness (lt_ill) will reduce the chances of working and therefore being unemployed. Note that our focus is on the relationship, not on establishing causation. Specifically we want to estimate the relationship between unemployment and long-term illness and we are interested in variations in OA-level unemployment by MSOAs so we will estimate the following two-level model:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1}x_{ij} + e_{ij}\\] MSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + u_{0j}\\] Replacing the first equation into the second, we have:\n\\[y_{ij} = (\\beta_{0} + u_{0j}) + \\beta_{1}x_{ij} + e_{ij}\\] where \\(y\\) the proportion of unemployed population in OA \\(i\\) within MSOA \\(j\\); \\(\\beta_{0}\\) is the fixed intercept (averaging over all MSOAs); \\(u_{0j}\\) represents the MSOA-level residuals or random effects; \\(\\beta_{0}\\) and \\(u_{0j}\\) together represent the varying-intercept; \\(\\beta_{1}\\) is the slope coefficient; \\(x_{ij}\\) represents the percentage of long-term illness population; and, \\(e_{ij}\\) is the individual-level residuals.\nWe estimate the model executing:\n\n# change to proportion\noa_shp$lt_ill <- lt_ill/100\n\n# specify a model equation\neq4 <- unemp ~ lt_ill + (1 | msoa_cd)\nmodel4 <- lmer(eq4, data = oa_shp)\n\n# estimates\nsummary(model4)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4711.9\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-5.1941 -0.5718 -0.0906 0.4507 5.9393 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n msoa_cd (Intercept) 0.001421 0.03769 \n Residual 0.002674 0.05171 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.04682 0.00625 7.492\nlt_ill 0.29588 0.01615 18.317\n\nCorrelation of Fixed Effects:\n (Intr)\nlt_ill -0.600\n\n\nFixed effects: model averaging over MSOAs\n\nfixef(model4)\n\n(Intercept) lt_ill \n 0.04681959 0.29588110 \n\n\nyields an estimated regression line in an average McSOA: \\(y = 0.04681959 + 0.29588110x\\)\nRandom effects: MSOA-level errors\n\nranef_m4 <- ranef(model4)\nhead(ranef_m4$msoa_cd, 5)\n\n (Intercept)\nE02001347 -0.017474815\nE02001348 -0.021203807\nE02001349 -0.022469313\nE02001350 -0.003539869\nE02001351 0.008502813\n\n\nyields an estimated intercept for MSOA E02001347 which is 0.017474815 lower than the average with a regression line: (0.04681959 - 0.017474815) + 0.29588110x = 0.02934478 + 0.29588110x. You can confirm this by looking at the estimated model within each MSOA by executing on the first row:\n\ncoef(model4) %>% head(n = 5 )\n\n$msoa_cd\n (Intercept) lt_ill\nE02001347 0.0293447796 0.2958811\nE02001348 0.0256157871 0.2958811\nE02001349 0.0243502820 0.2958811\nE02001350 0.0432797257 0.2958811\nE02001351 0.0553224074 0.2958811\nE02001352 0.0636246817 0.2958811\nE02001353 0.0160357811 0.2958811\nE02001354 0.0581675090 0.2958811\nE02001355 0.0528556223 0.2958811\nE02001356 0.1061228409 0.2958811\nE02001357 0.0582394764 0.2958811\nE02001358 0.0740589539 0.2958811\nE02001359 0.0174543833 0.2958811\nE02001360 0.0715947302 0.2958811\nE02001361 0.0466345080 0.2958811\nE02001362 0.0160157652 0.2958811\nE02001363 0.0815677365 0.2958811\nE02001364 0.0934291622 0.2958811\nE02001365 0.0919597741 0.2958811\nE02001366 0.0620614209 0.2958811\nE02001367 0.0030188157 0.2958811\nE02001368 0.0808079877 0.2958811\nE02001369 0.0632672806 0.2958811\nE02001370 0.1335873521 0.2958811\nE02001371 0.0515952786 0.2958811\nE02001372 0.0309188138 0.2958811\nE02001373 0.0545884863 0.2958811\nE02001374 0.1039777893 0.2958811\nE02001375 0.0409780838 0.2958811\nE02001376 0.0964558147 0.2958811\nE02001377 0.0558567086 0.2958811\nE02001378 0.0241577873 0.2958811\nE02001380 0.0046345234 0.2958811\nE02001381 0.0711500934 0.2958811\nE02001382 0.0064505905 0.2958811\nE02001383 0.0742504417 0.2958811\nE02001384 0.0490214750 0.2958811\nE02001385 0.1707802796 0.2958811\nE02001386 0.0336177791 0.2958811\nE02001387 -0.0007218010 0.2958811\nE02001388 0.0125049014 0.2958811\nE02001389 0.0711118539 0.2958811\nE02001390 0.0805482208 0.2958811\nE02001391 0.0417458225 0.2958811\nE02001392 -0.0074952916 0.2958811\nE02001393 -0.0051402516 0.2958811\nE02001394 0.0181501721 0.2958811\nE02001395 0.0009387908 0.2958811\nE02001396 0.0521380692 0.2958811\nE02001397 -0.0006698249 0.2958811\nE02001398 0.0197886833 0.2958811\nE02001399 0.0030131040 0.2958811\nE02001400 0.0274024412 0.2958811\nE02001401 -0.0043446188 0.2958811\nE02001402 0.0074558647 0.2958811\nE02001403 0.0539235547 0.2958811\nE02001404 0.0647550886 0.2958811\nE02001405 0.0903509760 0.2958811\nE02006932 0.0310245337 0.2958811\nE02006933 0.0276019142 0.2958811\nE02006934 0.0350623557 0.2958811\n\n\nFixed effect correlations\nIn the bottom of the output, we have the correlations between the fixed-effects estimates. In our example, it refers to the correlation between \\(\\beta_{0}\\) and \\(\\beta_{1}\\). It is negative indicating that in MSOAs where the relationship between unemployment and long-term illness is greater, as measured by \\(\\beta_{1}\\), the average proportion of unemployed people tends to be smaller, as captured by \\(\\beta_{0}\\).\n\n7.4.6 Adding Group-level Predictors\nWe can also add group-level predictors. We use the formulation:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1}x_{ij} + e_{ij}\\]\nMSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + \\gamma_{1}m_{j} + u_{0j}\\]\nwhere \\(x_{ij}\\) is the OA-level proportion of population suffering long-term illness and \\(m_{j}\\) is the MSOA-level proportion of male population. We first need to create this group-level predictor:\n\n# detach OA shp and attach MSOA shp\ndetach(oa_shp)\nattach(msoa_shp)\n\n# group-level predictor\nmsoa_shp$pr_male <- males/pop\n\n# remove geometries\nmsoa_df <- `st_geometry<-`(msoa_shp, NULL)\n\n# select variables\nmsoa_df <- msoa_df %>% dplyr::select(MSOA_CD, pop, pr_male)\n\n# merge data sets\noa_shp <- merge(x=oa_shp, y=msoa_df, by.x = \"msoa_cd\", by.y=\"MSOA_CD\")\n\n# inspect data\nhead(oa_shp[1:10, c(\"msoa_cd\", \"oa_cd\", \"unemp\", \"pr_male\")])\n\nSimple feature collection with 6 features and 4 fields\nGeometry type: POLYGON\nDimension: XY\nBounding box: xmin: 337693.5 ymin: 396068.2 xmax: 339430.9 ymax: 397790\nProjected CRS: Transverse_Mercator\n msoa_cd oa_cd unemp pr_male geometry\n1 E02001347 E00033730 0.10322581 0.4775905 POLYGON ((338376 397059, 33...\n2 E02001347 E00033722 0.06306306 0.4775905 POLYGON ((337929.4 397669.9...\n3 E02001347 E00033712 0.09090909 0.4775905 POLYGON ((338830 396068.2, ...\n4 E02001347 E00033739 0.09401709 0.4775905 POLYGON ((339140.3 397191, ...\n5 E02001347 E00033719 0.05855856 0.4775905 POLYGON ((338128.8 397658.6...\n6 E02001347 E00033711 0.12195122 0.4775905 POLYGON ((339163.2 396833.6...\n\n\nWe can now estimate our model:\n\ndetach(msoa_shp)\nattach(oa_shp)\n\n# specify a model equation\neq5 <- unemp ~ lt_ill + pr_male + (1 | msoa_cd)\nmodel5 <- lmer(eq5, data = oa_shp)\n\n# estimates\nsummary(model5)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + pr_male + (1 | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4712.3\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-5.2162 -0.5696 -0.0929 0.4549 5.9370 \n\nRandom effects:\n Groups Name Variance Std.Dev.\n msoa_cd (Intercept) 0.001391 0.03729 \n Residual 0.002674 0.05171 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) -0.07746 0.08768 -0.883\nlt_ill 0.29781 0.01620 18.389\npr_male 0.25059 0.17642 1.420\n\nCorrelation of Fixed Effects:\n (Intr) lt_ill\nlt_ill -0.118 \npr_male -0.997 0.075\n\n\nThis model includes the proportion of males and intercepts that vary by MSOA. The lmer() function only accepts predictors at the individual level, so we have included data on the proportion of male population at this level. Explore and interpret the model running the functions below:\n\n# fixed effects\nfixef(model5)\n\n(Intercept) lt_ill pr_male \n -0.0774607 0.2978084 0.2505913 \n\n\n\n# random effects\nranef_m5 <- ranef(model5)\nhead(ranef_m5$msoa_cd, 5)\n\n (Intercept)\nE02001347 -0.013625261\nE02001348 -0.019757846\nE02001349 -0.023709992\nE02001350 0.003003861\nE02001351 0.003508477\n\n\nAdding group-level predictors tends to improve inferences for group coefficients. Examine the confidence intervals, in order to evalute how the precision of our estimates of the MSOA intercepts have changed. Have confidence intervals for the intercepts of Model 4 and 5 increased or reduced? Hint: look at how to get the confidence intervals above.", + "crumbs": [ + "7  Multilevel Modelling - Part 1" + ] }, { "objectID": "07-multilevel-01.html#questions", "href": "07-multilevel-01.html#questions", "title": "7  Multilevel Modelling - Part 1", - "section": "7.5 Questions", - "text": "7.5 Questions\nFor the second assignment, we will be using a different dataset comprising information on COVID-19 cases, census data and the Index of Multiple Deprivation (IMD) for England. The data set is similar in structured to that used in this chapter. It is hierarchically organised into 149 Upper Tier Local Authority Districts (UTLADs) within 9 Regions and has 508 variables - see Chapter @ref(datasets) for a more detailed description of the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nHere we see a selection of 10 variables for 5 UTLADs.\n\nhead(sdf[1:5,c(3,4,9,10,381,385,386,387,403,406)])\n\nSimple feature collection with 5 features and 10 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 418871.2 ymin: 506329.3 xmax: 478441.5 ymax: 537152\nProjected CRS: OSGB36 / British National Grid\n ctyua19nm Region X2020.01.31 X2020.02.01 IMD...Average.score\n1 Hartlepool North East 0 0 35.037\n2 Middlesbrough North East 0 0 40.460\n3 Redcar and Cleveland North East 0 0 29.792\n4 Stockton-on-Tees North East 0 0 25.790\n5 Darlington North East 0 0 25.657\n Residents Households Dwellings Age_85plus White_British_and_Irish\n1 92028 40434 42102 1856 89117\n2 138412 57203 59956 2465 119680\n3 135177 59605 61899 3113 132343\n4 191610 79159 82237 3481 179501\n5 105564 46670 48644 2550 99226\n geom\n1 MULTIPOLYGON (((447097 5371...\n2 MULTIPOLYGON (((449862.8 52...\n3 MULTIPOLYGON (((455939.7 52...\n4 MULTIPOLYGON (((444126.1 52...\n5 MULTIPOLYGON (((423475.7 52...\n\n\n\nctyua19nm: Upper Tier Local Authority District name\nRegion: Region name\nX2020.01.31: COVID-19 cases on January 31st 2020\nX2020.02.01: COVID-19 cases on February 1st 2020\nIMD...Average.score: Average IMD score for UTLADs - see File 11: upper-tier local authority summaries for information on this and associated indicators.\nResidents: Number of residents\nHouseholds: Number of households\nDwellings: Number of dwellings\nAge_85plus: Number of people aged 85 and over\nWhite_British_and_Irish: Number of white British and Irish people\n\nNote that variable names relating to the daily COVID-19 cases are organised in the following way: X stands for daily COVID-19 cases, followed by the year (i.e. 2020, 2021); month (i.e. January to December); and day (i.e. 01 to 31).\nUsing these data, you are required to address the following challenges:\n\nFit a varying-intercept model with no explanatory variables. Let the intercept to vary by region.\nFit a varying-intercept model with including at least three explanatory variables.\nCompute the Variance Partition Coefficient (VPC) for the models estimated according to points 1 and 2 above.\nCreate caterpillar plots to visualise the varying intercepts.\n\nAnalyse and discuss: 1. the extent of variation in the dependent variables at the two geographical scales (variation at which geographical scale explains most of variance in your dependent variable); 2. the varying intercept estimate(s) from your model(s) (what can they tell you about the difference between groups / areas? are they statistically significantly different?);\nEnsure you appropriately describe the structure of the data and identify the various geographical scales of analysis (i.e. level-1 and level-2 units)\nIn addressing the challenges in this and following chapters, you have some flexibility to be creative. A set of key factors to consider:\n\nDependent Variable: We will seek to explain daily COVID-19 cases, and you will need to make a decision as to:\n\n\nDaily vs cumulative COVID-19 cases. Given that we will be focusing on cross-sectional models (i.e. models for one snapshot), you can focus on modelling daily cases at one specific date or cumulative daily cases over a period of time.\nTime period. You can select the date or period of time that you will focus your analysis on.\nUse risk of COVID-19 infection. The dependent variable should be the risk or rate of COVID-19 infection.\n\nFor example, the risk of COVID-19 infection for the period (i.e. between Dec. 1st, 2020 - January 29th, 2021) comprising the third wave of the pandemic in the United Kingdom can be computed as:\n\n# computing cumulative COVID cases for 01/12/2020 - 29/01/2021\nsdf[, 509] <- sdf %>% dplyr::select(\"X2020.12.01\":\"X2021.01.29\") %>% # select COVID cases 01/12/2020 - 29/01/2021\n mutate(cum_covid = rowSums(across(where(is.numeric)))) %>% # sum daily cases\n dplyr::select(cum_covid) %>% # select cumulative cases\n st_set_geometry(., NULL) # set geometry to NULL\n\n# computing risk of infection\nsdf <- sdf %>% mutate(\n covid19_r = round((cum_covid / Residents ) * 1000) \n )\n\n\nExplanatory variables:\n\n\nAt least 3. Use at least 3 explanatory variables. There is no maximum limit but consider your model to be parsimonious.\nChoice your set. Select the set of variables you consider appropriate / interesting. Make sure you justify your choice based on evidence and/or theory.\nPercentages / Proportions. Use percentages or proportions to capture the composition of places, rather than numbers of people, households or dwellings. For this, ensure you are using the appropriate denominator.\n\nFor instance, if you want to capture the relationship between cumulative COVID-19 cases and overcrowding, share of elderly population and nonwhite minorities, use the following variables\n\nsdf <- sdf %>% mutate(\n crowded_hou = Crowded_housing / Households, # share of crowded housing\n elderly = (Age_85plus) / Residents, # share of population aged 65+\n ethnic = (Mixed + Indian + Pakistani + Bangladeshi + Chinese + Other_Asian + Black + Other_ethnicity) / Residents, # share of nonwhite population\n)\n\nADVICE: Create a new spatial data frame including only the variables you will analyse. For example:\n\nnsdf <- sdf %>% dplyr::select(objectid, \n ctyua19cd, \n ctyua19nm, \n Region, \n covid19_r, \n crowded_hou, \n elderly, \n ethnic, \n Residents)\n\n\n\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel Modelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04." + "section": "\n7.5 Questions", + "text": "7.5 Questions\nFor the second assignment, we will be using a different dataset comprising information on COVID-19 cases, census data and the Index of Multiple Deprivation (IMD) for England. The data set is similar in structured to that used in this chapter. It is hierarchically organised into 149 Upper Tier Local Authority Districts (UTLADs) within 9 Regions and has 508 variables - see Chapter @ref(datasets) for a more detailed description of the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nHere we see a selection of 10 variables for 5 UTLADs.\n\nhead(sdf[1:5,c(3,4,9,10,381,385,386,387,403,406)])\n\nSimple feature collection with 5 features and 10 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 418871.2 ymin: 506329.3 xmax: 478441.5 ymax: 537152\nProjected CRS: OSGB36 / British National Grid\n ctyua19nm Region X2020.01.31 X2020.02.01 IMD...Average.score\n1 Hartlepool North East 0 0 35.037\n2 Middlesbrough North East 0 0 40.460\n3 Redcar and Cleveland North East 0 0 29.792\n4 Stockton-on-Tees North East 0 0 25.790\n5 Darlington North East 0 0 25.657\n Residents Households Dwellings Age_85plus White_British_and_Irish\n1 92028 40434 42102 1856 89117\n2 138412 57203 59956 2465 119680\n3 135177 59605 61899 3113 132343\n4 191610 79159 82237 3481 179501\n5 105564 46670 48644 2550 99226\n geom\n1 MULTIPOLYGON (((447097 5371...\n2 MULTIPOLYGON (((449862.8 52...\n3 MULTIPOLYGON (((455939.7 52...\n4 MULTIPOLYGON (((444126.1 52...\n5 MULTIPOLYGON (((423475.7 52...\n\n\n\n\nctyua19nm: Upper Tier Local Authority District name\n\nRegion: Region name\n\nX2020.01.31: COVID-19 cases on January 31st 2020\n\nX2020.02.01: COVID-19 cases on February 1st 2020\n\nIMD...Average.score: Average IMD score for UTLADs - see File 11: upper-tier local authority summaries for information on this and associated indicators.\n\nResidents: Number of residents\n\nHouseholds: Number of households\n\nDwellings: Number of dwellings\n\nAge_85plus: Number of people aged 85 and over\n\nWhite_British_and_Irish: Number of white British and Irish people\n\nNote that variable names relating to the daily COVID-19 cases are organised in the following way: X stands for daily COVID-19 cases, followed by the year (i.e. 2020, 2021); month (i.e. January to December); and day (i.e. 01 to 31).\nUsing these data, you are required to address the following challenges:\n\nFit a varying-intercept model with no explanatory variables. Let the intercept to vary by region.\nFit a varying-intercept model with including at least three explanatory variables.\nCompute the Variance Partition Coefficient (VPC) for the models estimated according to points 1 and 2 above.\nCreate caterpillar plots to visualise the varying intercepts.\n\nAnalyse and discuss: 1. the extent of variation in the dependent variables at the two geographical scales (variation at which geographical scale explains most of variance in your dependent variable); 2. the varying intercept estimate(s) from your model(s) (what can they tell you about the difference between groups / areas? are they statistically significantly different?);\nEnsure you appropriately describe the structure of the data and identify the various geographical scales of analysis (i.e. level-1 and level-2 units)\nIn addressing the challenges in this and following chapters, you have some flexibility to be creative. A set of key factors to consider:\n\n\nDependent Variable: We will seek to explain daily COVID-19 cases, and you will need to make a decision as to:\n\n\nDaily vs cumulative COVID-19 cases. Given that we will be focusing on cross-sectional models (i.e. models for one snapshot), you can focus on modelling daily cases at one specific date or cumulative daily cases over a period of time.\nTime period. You can select the date or period of time that you will focus your analysis on.\nUse risk of COVID-19 infection. The dependent variable should be the risk or rate of COVID-19 infection.\n\nFor example, the risk of COVID-19 infection for the period (i.e. between Dec. 1st, 2020 - January 29th, 2021) comprising the third wave of the pandemic in the United Kingdom can be computed as:\n\n# computing cumulative COVID cases for 01/12/2020 - 29/01/2021\nsdf[, 509] <- sdf %>% dplyr::select(\"X2020.12.01\":\"X2021.01.29\") %>% # select COVID cases 01/12/2020 - 29/01/2021\n mutate(cum_covid = rowSums(across(where(is.numeric)))) %>% # sum daily cases\n dplyr::select(cum_covid) %>% # select cumulative cases\n st_set_geometry(., NULL) # set geometry to NULL\n\n# computing risk of infection\nsdf <- sdf %>% mutate(\n covid19_r = round((cum_covid / Residents ) * 1000) \n )\n\n\n\nExplanatory variables:\n\n\nAt least 3. Use at least 3 explanatory variables. There is no maximum limit but consider your model to be parsimonious.\nChoice your set. Select the set of variables you consider appropriate / interesting. Make sure you justify your choice based on evidence and/or theory.\nPercentages / Proportions. Use percentages or proportions to capture the composition of places, rather than numbers of people, households or dwellings. For this, ensure you are using the appropriate denominator.\n\nFor instance, if you want to capture the relationship between cumulative COVID-19 cases and overcrowding, share of elderly population and nonwhite minorities, use the following variables\n\nsdf <- sdf %>% mutate(\n crowded_hou = Crowded_housing / Households, # share of crowded housing\n elderly = (Age_85plus) / Residents, # share of population aged 65+\n ethnic = (Mixed + Indian + Pakistani + Bangladeshi + Chinese + Other_Asian + Black + Other_ethnicity) / Residents, # share of nonwhite population\n)\n\nADVICE: Create a new spatial data frame including only the variables you will analyse. For example:\n\nnsdf <- sdf %>% dplyr::select(objectid, \n ctyua19cd, \n ctyua19nm, \n Region, \n covid19_r, \n crowded_hou, \n elderly, \n ethnic, \n Residents)\n\n\n\n\n\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel Modelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04.", + "crumbs": [ + "7  Multilevel Modelling - Part 1" + ] }, { - "objectID": "08-multilevel-02.html#dependencies", - "href": "08-multilevel-02.html#dependencies", + "objectID": "08-multilevel-02.html", + "href": "08-multilevel-02.html", "title": "8  Multilevel Modelling - Part 2", - "section": "8.1 Dependencies", - "text": "8.1 Dependencies\nThis chapter uses the following libraries which are listed in the Section 1.4.1 in Chapter 1:\n\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(RColorBrewer) \n# More colour palettes\nlibrary(viridis) # nice colour schemes\n# Fitting multilevel models\nlibrary(lme4)\n# Tools for extracting information generated by lme4\nlibrary(merTools)\n# Exportable regression tables\nlibrary(jtools)\nlibrary(stargazer)\nlibrary(sjPlot)" + "section": "", + "text": "8.1 Dependencies\nThis chapter uses the following libraries which are listed in the Section 1.4.1 in Chapter 1:\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(viridis) \n# Fitting multilevel models\nlibrary(lme4)\n# Tools for extracting information generated by lme4\nlibrary(merTools)\n# Exportable regression tables\nlibrary(jtools)", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#data", "href": "08-multilevel-02.html#data", "title": "8  Multilevel Modelling - Part 2", - "section": "8.2 Data", - "text": "8.2 Data\nFor this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1\nLet us read the data:\n\n# clean workspace\nrm(list=ls())\n# read data\noa_shp <- st_read(\"data/mlm/OA.shp\")" + "section": "\n8.2 Data", + "text": "8.2 Data\nFor this chapter, we will data for Liverpool from England’s 2011 Census. The original source is the Office of National Statistics and the dataset comprises a number of selected variables capturing demographic, health and socio-economic of the local resident population at four geographic levels: Output Area (OA), Lower Super Output Area (LSOA), Middle Super Output Area (MSOA) and Local Authority District (LAD). The variables include population counts and percentages. For a description of the variables, see the readme file in the mlm data folder.1\n1 Read the file in R by executing read_tsv(\"data/mlm/readme.txt\") . Ensure the library readr is installed before running read_tsv.Let us read the data:\n\n# clean workspace\nrm(list=ls())\n# read data\noa_shp <- st_read(\"data/mlm/OA.shp\")", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#conceptual-overview", "href": "08-multilevel-02.html#conceptual-overview", "title": "8  Multilevel Modelling - Part 2", - "section": "8.3 Conceptual Overview", - "text": "8.3 Conceptual Overview\nSo far, we have estimated varying-intercept models; that is, when the intercept (\\(\\beta_{0}\\)) is allowed to vary by group (eg. geographical area) - as shown in Fig. 1(a). The strength of the relationship between \\(y\\) (i.e. unemployment rate) and \\(x\\) (long-term illness) has been assumed to be the same across groups (i.e. MSOAs), as captured by the regression slope (\\(\\beta_{1}\\)). Yet it can also vary by group as shown in Fig. 1(b), or we can observe group variability for both intercepts and slopes as represented in Fig. 1(c).\n\n\n\nFig. 1. Linear regression model with (a) varying intercepts, (b) varying slopes, and (c) both. Source: Gelman and Hill (2006) p.238.\n\n\n\n8.3.1 Exploratory Analysis: Varying Slopes\nLet’s then explore if there is variation in the relationship between unemployment rate and the share of population in long-term illness. We do this by selecting the 8 MSOAs containing OAs with the highest unemployment rates in Liverpool.\n\n# Sort data \noa_shp <- oa_shp %>% arrange(-unemp)\noa_shp[1:9, c(\"msoa_cd\", \"unemp\")]\n\nSimple feature collection with 9 features and 2 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335032 ymin: 387777 xmax: 338576.1 ymax: 395022.4\nProjected CRS: Transverse_Mercator\n msoa_cd unemp geometry\n1 E02001354 0.5000000 MULTIPOLYGON (((337491.2 39...\n2 E02001369 0.4960630 MULTIPOLYGON (((335272.3 39...\n3 E02001366 0.4461538 MULTIPOLYGON (((338198.1 39...\n4 E02001365 0.4352941 MULTIPOLYGON (((336572.2 39...\n5 E02001370 0.4024390 MULTIPOLYGON (((336328.3 39...\n6 E02001390 0.3801653 MULTIPOLYGON (((335833.6 38...\n7 E02001354 0.3750000 MULTIPOLYGON (((337403 3949...\n8 E02001385 0.3707865 MULTIPOLYGON (((336251.6 38...\n9 E02001368 0.3648649 MULTIPOLYGON (((335209.3 39...\n\n# Select MSOAs\ns_t8 <- oa_shp %>% dplyr::filter(\n as.character(msoa_cd) %in% c(\n \"E02001354\", \n \"E02001369\", \n \"E02001366\", \n \"E02001365\", \n \"E02001370\", \n \"E02001390\", \n \"E02001368\", \n \"E02001385\")\n )\n\nAnd then we generate a set of scatter plots and draw regression lines for each MSOA.\n\nggplot(s_t8, aes(x = lt_ill, y = unemp)) + \n geom_point() + \n geom_smooth(method = \"lm\") +\n facet_wrap(~ msoa_cd, nrow = 2) +\n ylab(\"Unemployment rate\") + \n xlab(\"Long-term Illness (%)\") +\n theme_classic()\n\n`geom_smooth()` using formula = 'y ~ x'\n\n\n\n\n\nWe can observe great variability in the relationship between unemployment rates and the percentage of population in long-term illness. A strong and positive relationship exists in MSOA E02001366 (Tuebrook and Stoneycroft), while it is negative in MSOA E02001370 (Everton) and neutral in MSOA E02001390 (Princes Park & Riverside). This visual inspection suggests that accounting for differences in the way unmployment rates relate to long-term illness is important. Contextual factors may differ across MSOAs in systematic ways." + "section": "\n8.3 Conceptual Overview", + "text": "8.3 Conceptual Overview\nSo far, we have estimated varying-intercept models; that is, when the intercept (\\(\\beta_{0}\\)) is allowed to vary by group (eg. geographical area) - as shown in Fig. 1(a). The strength of the relationship between \\(y\\) (i.e. unemployment rate) and \\(x\\) (long-term illness) has been assumed to be the same across groups (i.e. MSOAs), as captured by the regression slope (\\(\\beta_{1}\\)). Yet it can also vary by group as shown in Fig. 1(b), or we can observe group variability for both intercepts and slopes as represented in Fig. 1(c).\n\n\nFig. 1. Linear regression model with (a) varying intercepts, (b) varying slopes, and (c) both. Source: Gelman and Hill (2006) p.238.\n\n\n8.3.1 Exploratory Analysis: Varying Slopes\nLet’s then explore if there is variation in the relationship between unemployment rate and the share of population in long-term illness. We do this by selecting the 8 MSOAs containing OAs with the highest unemployment rates in Liverpool.\n\n# Sort data \noa_shp <- oa_shp %>% arrange(-unemp)\noa_shp[1:9, c(\"msoa_cd\", \"unemp\")]\n\nSimple feature collection with 9 features and 2 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335032 ymin: 387777 xmax: 338576.1 ymax: 395022.4\nProjected CRS: Transverse_Mercator\n msoa_cd unemp geometry\n1 E02001354 0.5000000 MULTIPOLYGON (((337491.2 39...\n2 E02001369 0.4960630 MULTIPOLYGON (((335272.3 39...\n3 E02001366 0.4461538 MULTIPOLYGON (((338198.1 39...\n4 E02001365 0.4352941 MULTIPOLYGON (((336572.2 39...\n5 E02001370 0.4024390 MULTIPOLYGON (((336328.3 39...\n6 E02001390 0.3801653 MULTIPOLYGON (((335833.6 38...\n7 E02001354 0.3750000 MULTIPOLYGON (((337403 3949...\n8 E02001385 0.3707865 MULTIPOLYGON (((336251.6 38...\n9 E02001368 0.3648649 MULTIPOLYGON (((335209.3 39...\n\n# Select MSOAs\ns_t8 <- oa_shp %>% dplyr::filter(\n as.character(msoa_cd) %in% c(\n \"E02001354\", \n \"E02001369\", \n \"E02001366\", \n \"E02001365\", \n \"E02001370\", \n \"E02001390\", \n \"E02001368\", \n \"E02001385\")\n )\n\nAnd then we generate a set of scatter plots and draw regression lines for each MSOA.\n\nggplot(s_t8, aes(x = lt_ill, y = unemp)) + \n geom_point() + \n geom_smooth(method = \"lm\") +\n facet_wrap(~ msoa_cd, nrow = 2) +\n ylab(\"Unemployment rate\") + \n xlab(\"Long-term Illness (%)\") +\n theme_classic()\n\n`geom_smooth()` using formula = 'y ~ x'\n\n\n\n\n\n\n\n\nWe can observe great variability in the relationship between unemployment rates and the percentage of population in long-term illness. A strong and positive relationship exists in MSOA E02001366 (Tuebrook and Stoneycroft), while it is negative in MSOA E02001370 (Everton) and neutral in MSOA E02001390 (Princes Park & Riverside). This visual inspection suggests that accounting for differences in the way unmployment rates relate to long-term illness is important. Contextual factors may differ across MSOAs in systematic ways.", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#estimating-varying-intercept-and-slopes-models", "href": "08-multilevel-02.html#estimating-varying-intercept-and-slopes-models", "title": "8  Multilevel Modelling - Part 2", - "section": "8.4 Estimating Varying Intercept and Slopes Models", - "text": "8.4 Estimating Varying Intercept and Slopes Models\nA way to capture for these group differences in the relationship between unemployment rates and long-term illness is to allow the relevant slope to vary by group (i.e. MSOA). We can do this estimating the following model:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1j}x_{ij} + e_{ij}\\]\nMSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + u_{0j}\\] \\[\\beta_{1j} = \\beta_{1} + u_{1j} \\] Replacing the first equation into the second generates:\n\\[y_{ij} = (\\beta_{0} + u_{0j}) + (\\beta_{1} + u_{1j})x_{ij} + e_{ij}\\] where, as in the previous Chapter, \\(y\\) the proportion of unemployed population in OA \\(i\\) within MSOA \\(j\\); \\(\\beta_{0}\\) is the fixed intercept (averaging over all MSOAs); \\(u_{0j}\\) represents the MSOA-level residuals, or random effects, of the intercept; \\(e_{ij}\\) is the individual-level residuals; and, \\(x_{ij}\\) represents the percentage of long-term illness population. But now we have a varying slope represented by \\(\\beta_{1}\\) and \\(u_{1j}\\): \\(\\beta_{1}\\) is estimated average slope - fixed part of the model; and, \\(u_{1j}\\) is the estimated group-level errors of the slope.\nTo estimate such model, we add lt_ill in the bracket with a + sign between 1 and | i.e. (1 + lt_ill | msoa_cd).\n\n# attach df\nattach(oa_shp)\n\n# change to proportion\noa_shp$lt_ill <- lt_ill/100\n\n# specify a model equation\neq6 <- unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\nmodel6 <- lmer(eq6, data = oa_shp)\n\n# estimates\nsummary(model6)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4762.8\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-3.6639 -0.5744 -0.0873 0.4565 5.4876 \n\nRandom effects:\n Groups Name Variance Std.Dev. Corr \n msoa_cd (Intercept) 0.003428 0.05855 \n lt_ill 0.029425 0.17154 -0.73\n Residual 0.002474 0.04974 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.047650 0.008635 5.519\nlt_ill 0.301259 0.028162 10.697\n\nCorrelation of Fixed Effects:\n (Intr)\nlt_ill -0.786\n\n\nIn this model, the estimated standard deviation of the unexplained within-MSOA variation is 0.04974, and the estimated standard deviation of the MSOA intercepts is 0.05855. But, additionally, we also have estimates of standard deviation of the MSOA slopes (0.17154) and correlation between MSOA-level residuals for the intercept and slope (-0.73). While the former measures the extent of average deviation in the slopes across MSOAs, the latter indicates that the intercept and slope MSOA-level residuals are negatively associated; that is, MSOAs with large slopes have relatively smaller intercepts and vice versa. We will come back to this in Section Interpreting Correlations Between Group-level Intercepts and Slopes.\nSimilarly, the correlation of fixed effects indicates a negative relationship between the intercept and slope of the average regression model; that is, as the average model intercept tends to increase, the average strength of the relationship between unemployment rate and long-term illness decreases and vice versa.\nWe then explore the estimated average coefficients (fixed effects):\n\nfixef(model6)\n\n(Intercept) lt_ill \n 0.04765009 0.30125875 \n\n\nyields an estimated regression line in an average LSOA: \\(y = 0.04764998 + 0.30125916x\\). The fixed intercept indicates that the average unemployment rate is 0.05 if the percentage of population with long-term illness is zero.The fixed slope indicates that the average relationship between unemployment rate and long-term illness is positive across MSOAs i.e. as the percentage of population with long-term illness increases by 1 percentage point, the unemployment rate increases by 0.3.\nWe look the estimated MSOA-level errors (random effects):\n\nranef_m6 <- ranef(model6)\nhead(ranef_m6$msoa_cd, 5)\n\n (Intercept) lt_ill\nE02001347 -0.026561345 0.02718102\nE02001348 0.001688245 -0.11533102\nE02001349 -0.036084817 0.05547075\nE02001350 0.032240842 -0.14298734\nE02001351 0.086214137 -0.28130162\n\n\nRecall these estimates indicate the extent of deviation of the MSOA-specific intercept and slope from the estimated model average captured by the fixed model component.\nWe can also regain the estimated intercept and slope for each county by adding the estimated MSOA-level errors to the estimated average coefficients; or by executing:\n\n#coef(model6)\n\nWe are normally more interested in identifying the extent of deviation and its significance. To this end, we create a caterpillar plot:\n\n# plot\nplotREsim(REsim(model6))\n\n\n\n\nThese plots reveal some interesting patterns. First, only one MSOA, containing wards such as Tuebrook and Stoneycroft, Anfield & Everton, seems to have a statistically significantly different intercept, or average unemployment rate. Confidence intervals overlap zero for all other 60 MSOAs. Despite this, note that when a slope is allowed to vary by group, it generally makes sense for the intercept to also vary. Second, significant variability exists in the association between unemployment rate and long-term illness across MSOAs. Ten MSOAs display a significant positive association, while 12 exhibit a significantly negative relationship. Third, these results reveal that geographical differences in the relationship between unemployment rate and long-term illness can explain the significant differences in average unemployment rates in the varying intercept only model.\nLet’s try to get a better understanding of the varying relationship between unemployment rate and long-term illness by mapping the relevant MSOA-level errors.\n\n# read data\nmsoa_shp <- st_read(\"data/mlm/MSOA.shp\")\n\nReading layer `MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/mlm/MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 17 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# create a dataframe for MSOA-level random effects\nre_msoa_m6 <- REsim(model6) %>% filter(groupFctr == \"msoa_cd\") %>%\n filter(term == \"lt_ill\")\nstr(re_msoa_m6)\n\n'data.frame': 61 obs. of 6 variables:\n $ groupFctr: chr \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" ...\n $ groupID : chr \"E02001347\" \"E02001348\" \"E02001349\" \"E02001350\" ...\n $ term : chr \"lt_ill\" \"lt_ill\" \"lt_ill\" \"lt_ill\" ...\n $ mean : num 0.0338 -0.1135 0.0579 -0.1473 -0.2838 ...\n $ median : num 0.0328 -0.1167 0.0591 -0.1486 -0.2833 ...\n $ sd : num 0.0463 0.0686 0.0789 0.0388 0.0415 ...\n\n# merge data\nmsoa_shp <- merge(x = msoa_shp, y = re_msoa_m6, by.x = \"MSOA_CD\", by.y = \"groupID\")\n\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nlegend_title = expression(\"MSOA-level residuals\")\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"median\", title = legend_title, palette = magma(256, begin = 0, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_msoa\n\n\n\n\nThe map indicates that the relationship between unemployment rate and long-term illness is tends to stronger and positive in northern MSOAs; that is, the percentage of population with long-term illness explains a greater share of the variation in unemployment rates in these locations. As expected, a greater share of population in long-term illness is associated with higher local unemployment. In contrast, the relationship between unemployment rate and long-term illness tends to operate in the reverse direction in north-east and middle-southern MSOAs. In these MSOAs, OAs tend to have a higher unemployment rate relative the share of population in long-term illness. You can confirm this examining the data for specific MSOA executing:\n\noa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp, lt_ill) %>%\n filter(as.character(msoa_cd) == \"E02001370\")\n\nSimple feature collection with 23 features and 4 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335885 ymin: 391134.2 xmax: 337596.3 ymax: 392467\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n msoa_cd ward_nm unemp lt_ill\n1 E02001370 Everton 0.4024390 0.2792793\n2 E02001370 Tuebrook and Stoneycroft 0.3561644 0.3391813\n3 E02001370 Everton 0.3285714 0.3106383\n4 E02001370 Everton 0.3209877 0.3283019\n5 E02001370 Anfield 0.3082707 0.1785714\n6 E02001370 Everton 0.3000000 0.4369501\n7 E02001370 Everton 0.2886598 0.3657143\n8 E02001370 Everton 0.2727273 0.3375000\n9 E02001370 Everton 0.2705882 0.2534247\n10 E02001370 Tuebrook and Stoneycroft 0.2661290 0.2941176\n geometry\n1 MULTIPOLYGON (((336328.3 39...\n2 MULTIPOLYGON (((337481.5 39...\n3 MULTIPOLYGON (((336018.5 39...\n4 MULTIPOLYGON (((336475.7 39...\n5 MULTIPOLYGON (((337110.6 39...\n6 MULTIPOLYGON (((336516.3 39...\n7 MULTIPOLYGON (((336668.6 39...\n8 MULTIPOLYGON (((336173.8 39...\n9 MULTIPOLYGON (((336870 3917...\n10 MULTIPOLYGON (((337363.8 39...\n\n\nNow try adding a group-level predictor and an individual-level predictor to the model. Unsure, look at Section 7.4.5 and Section 7.4.6 in Chapter 7." + "section": "\n8.4 Estimating Varying Intercept and Slopes Models", + "text": "8.4 Estimating Varying Intercept and Slopes Models\nA way to capture for these group differences in the relationship between unemployment rates and long-term illness is to allow the relevant slope to vary by group (i.e. MSOA). We can do this estimating the following model:\nOA-level:\n\\[y_{ij} = \\beta_{0j} + \\beta_{1j}x_{ij} + e_{ij}\\]\nMSOA-level:\n\\[\\beta_{0j} = \\beta_{0} + u_{0j}\\] \\[\\beta_{1j} = \\beta_{1} + u_{1j} \\] Replacing the first equation into the second generates:\n\\[y_{ij} = (\\beta_{0} + u_{0j}) + (\\beta_{1} + u_{1j})x_{ij} + e_{ij}\\] where, as in the previous Chapter, \\(y\\) the proportion of unemployed population in OA \\(i\\) within MSOA \\(j\\); \\(\\beta_{0}\\) is the fixed intercept (averaging over all MSOAs); \\(u_{0j}\\) represents the MSOA-level residuals, or random effects, of the intercept; \\(e_{ij}\\) is the individual-level residuals; and, \\(x_{ij}\\) represents the percentage of long-term illness population. But now we have a varying slope represented by \\(\\beta_{1}\\) and \\(u_{1j}\\): \\(\\beta_{1}\\) is estimated average slope - fixed part of the model; and, \\(u_{1j}\\) is the estimated group-level errors of the slope.\nTo estimate such model, we add lt_ill in the bracket with a + sign between 1 and | i.e. (1 + lt_ill | msoa_cd).\n\n# attach df\nattach(oa_shp)\n\n# change to proportion\noa_shp$lt_ill <- lt_ill/100\n\n# specify a model equation\neq6 <- unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\nmodel6 <- lmer(eq6, data = oa_shp)\n\n# estimates\nsummary(model6)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4762.8\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-3.6639 -0.5744 -0.0873 0.4565 5.4876 \n\nRandom effects:\n Groups Name Variance Std.Dev. Corr \n msoa_cd (Intercept) 0.003428 0.05855 \n lt_ill 0.029425 0.17154 -0.73\n Residual 0.002474 0.04974 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.047650 0.008635 5.519\nlt_ill 0.301259 0.028162 10.697\n\nCorrelation of Fixed Effects:\n (Intr)\nlt_ill -0.786\n\n\nIn this model, the estimated standard deviation of the unexplained within-MSOA variation is 0.04974, and the estimated standard deviation of the MSOA intercepts is 0.05855. But, additionally, we also have estimates of standard deviation of the MSOA slopes (0.17154) and correlation between MSOA-level residuals for the intercept and slope (-0.73). While the former measures the extent of average deviation in the slopes across MSOAs, the latter indicates that the intercept and slope MSOA-level residuals are negatively associated; that is, MSOAs with large slopes have relatively smaller intercepts and vice versa. We will come back to this in Section Interpreting Correlations Between Group-level Intercepts and Slopes.\nSimilarly, the correlation of fixed effects indicates a negative relationship between the intercept and slope of the average regression model; that is, as the average model intercept tends to increase, the average strength of the relationship between unemployment rate and long-term illness decreases and vice versa.\nWe then explore the estimated average coefficients (fixed effects):\n\nfixef(model6)\n\n(Intercept) lt_ill \n 0.04765009 0.30125875 \n\n\nyields an estimated regression line in an average LSOA: \\(y = 0.04764998 + 0.30125916x\\). The fixed intercept indicates that the average unemployment rate is 0.05 if the percentage of population with long-term illness is zero.The fixed slope indicates that the average relationship between unemployment rate and long-term illness is positive across MSOAs i.e. as the percentage of population with long-term illness increases by 1 percentage point, the unemployment rate increases by 0.3.\nWe look the estimated MSOA-level errors (random effects):\n\nranef_m6 <- ranef(model6)\nhead(ranef_m6$msoa_cd, 5)\n\n (Intercept) lt_ill\nE02001347 -0.026561345 0.02718102\nE02001348 0.001688245 -0.11533102\nE02001349 -0.036084817 0.05547075\nE02001350 0.032240842 -0.14298734\nE02001351 0.086214137 -0.28130162\n\n\nRecall these estimates indicate the extent of deviation of the MSOA-specific intercept and slope from the estimated model average captured by the fixed model component.\nWe can also regain the estimated intercept and slope for each county by adding the estimated MSOA-level errors to the estimated average coefficients; or by executing:\n\n#coef(model6)\n\nWe are normally more interested in identifying the extent of deviation and its significance. To this end, we create a caterpillar plot:\n\n# plot\nplotREsim(REsim(model6))\n\n\n\n\n\n\n\nThese plots reveal some interesting patterns. First, only one MSOA, containing wards such as Tuebrook and Stoneycroft, Anfield & Everton, seems to have a statistically significantly different intercept, or average unemployment rate. Confidence intervals overlap zero for all other 60 MSOAs. Despite this, note that when a slope is allowed to vary by group, it generally makes sense for the intercept to also vary. Second, significant variability exists in the association between unemployment rate and long-term illness across MSOAs. Ten MSOAs display a significant positive association, while 12 exhibit a significantly negative relationship. Third, these results reveal that geographical differences in the relationship between unemployment rate and long-term illness can explain the significant differences in average unemployment rates in the varying intercept only model.\nLet’s try to get a better understanding of the varying relationship between unemployment rate and long-term illness by mapping the relevant MSOA-level errors.\n\n# read data\nmsoa_shp <- st_read(\"data/mlm/MSOA.shp\")\n\nReading layer `MSOA' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/mlm/MSOA.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 61 features and 17 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 333086.1 ymin: 381426.3 xmax: 345636 ymax: 397980.1\nProjected CRS: Transverse_Mercator\n\n# create a dataframe for MSOA-level random effects\nre_msoa_m6 <- REsim(model6) %>% filter(groupFctr == \"msoa_cd\") %>%\n filter(term == \"lt_ill\")\nstr(re_msoa_m6)\n\n'data.frame': 61 obs. of 6 variables:\n $ groupFctr: chr \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" \"msoa_cd\" ...\n $ groupID : chr \"E02001347\" \"E02001348\" \"E02001349\" \"E02001350\" ...\n $ term : chr \"lt_ill\" \"lt_ill\" \"lt_ill\" \"lt_ill\" ...\n $ mean : num 0.026 -0.1126 0.0516 -0.1393 -0.2795 ...\n $ median : num 0.026 -0.1147 0.0587 -0.1422 -0.2815 ...\n $ sd : num 0.0427 0.0746 0.0826 0.0356 0.0401 ...\n\n# merge data\nmsoa_shp <- merge(x = msoa_shp, y = re_msoa_m6, by.x = \"MSOA_CD\", by.y = \"groupID\")\n\n\n# ensure geometry is valid\nmsoa_shp = sf::st_make_valid(msoa_shp)\n\n# create a map\nlegend_title = expression(\"MSOA-level residuals\")\nmap_msoa = tm_shape(msoa_shp) +\n tm_fill(col = \"median\", title = legend_title, palette = magma(256, begin = 0, end = 1), style = \"cont\") + \n tm_borders(col = \"white\", lwd = .01) + \n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 4) + \n tm_scale_bar(breaks = c(0,1,2), text.size = 0.5, position = c(\"center\", \"bottom\")) \nmap_msoa\n\n\n\n\n\n\n\nThe map indicates that the relationship between unemployment rate and long-term illness is tends to stronger and positive in northern MSOAs; that is, the percentage of population with long-term illness explains a greater share of the variation in unemployment rates in these locations. As expected, a greater share of population in long-term illness is associated with higher local unemployment. In contrast, the relationship between unemployment rate and long-term illness tends to operate in the reverse direction in north-east and middle-southern MSOAs. In these MSOAs, OAs tend to have a higher unemployment rate relative the share of population in long-term illness. You can confirm this examining the data for specific MSOA executing:\n\noa_shp %>% dplyr::select(msoa_cd, ward_nm, unemp, lt_ill) %>%\n filter(as.character(msoa_cd) == \"E02001370\")\n\nSimple feature collection with 23 features and 4 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 335885 ymin: 391134.2 xmax: 337596.3 ymax: 392467\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n msoa_cd ward_nm unemp lt_ill\n1 E02001370 Everton 0.4024390 0.2792793\n2 E02001370 Tuebrook and Stoneycroft 0.3561644 0.3391813\n3 E02001370 Everton 0.3285714 0.3106383\n4 E02001370 Everton 0.3209877 0.3283019\n5 E02001370 Anfield 0.3082707 0.1785714\n6 E02001370 Everton 0.3000000 0.4369501\n7 E02001370 Everton 0.2886598 0.3657143\n8 E02001370 Everton 0.2727273 0.3375000\n9 E02001370 Everton 0.2705882 0.2534247\n10 E02001370 Tuebrook and Stoneycroft 0.2661290 0.2941176\n geometry\n1 MULTIPOLYGON (((336328.3 39...\n2 MULTIPOLYGON (((337481.5 39...\n3 MULTIPOLYGON (((336018.5 39...\n4 MULTIPOLYGON (((336475.7 39...\n5 MULTIPOLYGON (((337110.6 39...\n6 MULTIPOLYGON (((336516.3 39...\n7 MULTIPOLYGON (((336668.6 39...\n8 MULTIPOLYGON (((336173.8 39...\n9 MULTIPOLYGON (((336870 3917...\n10 MULTIPOLYGON (((337363.8 39...\n\n\nNow try adding a group-level predictor and an individual-level predictor to the model. Unsure, look at Section 7.4.5 and Section 7.4.6 in Chapter 7.", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#interpreting-correlations-between-group-level-intercepts-and-slopes", "href": "08-multilevel-02.html#interpreting-correlations-between-group-level-intercepts-and-slopes", "title": "8  Multilevel Modelling - Part 2", - "section": "8.5 Interpreting Correlations Between Group-level Intercepts and Slopes", - "text": "8.5 Interpreting Correlations Between Group-level Intercepts and Slopes\nCorrelations of random effects are confusing to interpret. Key for their appropriate interpretation is to recall they refer to group-level residuals i.e. deviation of intercepts and slopes from the average model intercept and slope. A strong negative correlation indicates that groups with high intercepts have relatively low slopes, and vice versa. A strong positive correlation indicates that groups with high intercepts have relatively high slopes, and vice versa. A correlation close to zero indicate little or no systematic between intercepts and slopes. Note that a high correlation between intercepts and slopes is not a problem, but it makes the interpretation of the estimated intercepts more challenging. For this reason, a suggestion is to center predictors (\\(x's\\)); that is, substract their average value (\\(z = x - \\bar{x}\\)). For a more detailed discussion, see Multilevel Modelling (n.d.).\nTo illustrate this, let’s reestimate our model adding an individual-level predictor: the share of population with no educational qualification.\n\n# centering to the mean\noa_shp$z_no_qual <- no_qual/100 - mean(no_qual/100)\noa_shp$z_lt_ill <- lt_ill - mean(lt_ill)\n\n# specify a model equation\neq7 <- unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\nmodel7 <- lmer(eq7, data = oa_shp)\n\n# estimates\nsummary(model7)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4940.7\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-3.6830 -0.5949 -0.0868 0.4631 6.3556 \n\nRandom effects:\n Groups Name Variance Std.Dev. Corr \n msoa_cd (Intercept) 8.200e-04 0.02864 \n z_lt_ill 2.161e-06 0.00147 -0.04\n Residual 2.246e-03 0.04739 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.1163682 0.0039201 29.68\nz_lt_ill -0.0003130 0.0003404 -0.92\nz_no_qual 0.3245811 0.0221347 14.66\n\nCorrelation of Fixed Effects:\n (Intr) z_lt_l\nz_lt_ill -0.007 \nz_no_qual -0.015 -0.679\n\n\nHow do you interpret the random effect correlation?" + "section": "\n8.5 Interpreting Correlations Between Group-level Intercepts and Slopes", + "text": "8.5 Interpreting Correlations Between Group-level Intercepts and Slopes\nCorrelations of random effects are confusing to interpret. Key for their appropriate interpretation is to recall they refer to group-level residuals i.e. deviation of intercepts and slopes from the average model intercept and slope. A strong negative correlation indicates that groups with high intercepts have relatively low slopes, and vice versa. A strong positive correlation indicates that groups with high intercepts have relatively high slopes, and vice versa. A correlation close to zero indicate little or no systematic between intercepts and slopes. Note that a high correlation between intercepts and slopes is not a problem, but it makes the interpretation of the estimated intercepts more challenging. For this reason, a suggestion is to center predictors (\\(x's\\)); that is, substract their average value (\\(z = x - \\bar{x}\\)). For a more detailed discussion, see Multilevel Modelling (n.d.).\nTo illustrate this, let’s reestimate our model adding an individual-level predictor: the share of population with no educational qualification.\n\n# centering to the mean\noa_shp$z_no_qual <- no_qual/100 - mean(no_qual/100)\noa_shp$z_lt_ill <- lt_ill - mean(lt_ill)\n\n# specify a model equation\neq7 <- unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\nmodel7 <- lmer(eq7, data = oa_shp)\n\n# estimates\nsummary(model7)\n\nLinear mixed model fit by REML ['lmerMod']\nFormula: unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\n Data: oa_shp\n\nREML criterion at convergence: -4940.7\n\nScaled residuals: \n Min 1Q Median 3Q Max \n-3.6830 -0.5949 -0.0868 0.4631 6.3556 \n\nRandom effects:\n Groups Name Variance Std.Dev. Corr \n msoa_cd (Intercept) 8.200e-04 0.02864 \n z_lt_ill 2.161e-06 0.00147 -0.04\n Residual 2.246e-03 0.04739 \nNumber of obs: 1584, groups: msoa_cd, 61\n\nFixed effects:\n Estimate Std. Error t value\n(Intercept) 0.1163682 0.0039201 29.68\nz_lt_ill -0.0003130 0.0003404 -0.92\nz_no_qual 0.3245811 0.0221347 14.66\n\nCorrelation of Fixed Effects:\n (Intr) z_lt_l\nz_lt_ill -0.007 \nz_no_qual -0.015 -0.679\n\n\nHow do you interpret the random effect correlation?", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#model-building", "href": "08-multilevel-02.html#model-building", "title": "8  Multilevel Modelling - Part 2", - "section": "8.6 Model building", - "text": "8.6 Model building\nNow we know how to estimate multilevel regression models in R. The question that remains is: When does multilevel modeling make a difference? The short answer is: when there is little group-level variation. When there is very little group-level variation, the multilevel modelling reduces to classical linear regression estimates with no group indicators. Inversely, when group-level coefficients vary greatly (compared to their standard errors of estimation), multilevel modelling reduces to classical regression with group indicators Gelman and Hill (2006).\nHow do you go about building a model?\nWe generally start simple by fitting simple linear regressions and then work our way up to a full multilevel model - see Gelman and Hill (2006) p. 270.\nHow many groups are needed?\nAs an absolute minimum, more than two groups are required. With only one or two groups, a multilevel model reduces to a linear regression model.\nHow many observations per group?\nTwo observations per group is sufficient to fit a multilevel model.\n\n8.6.1 Model Comparison\nHow we assess different candidate models? We can use the function anova() and assess various statistics: The Akaike Information Criterion (AIC), the Bayesian Information Criterion (BIC), Loglik and Deviance. Generally, we look for lower scores for all these indicators. We can also refer to the Chisq statistic below. It tests the hypothesis of whether additional predictors improve model fit. Particularly it tests the Null Hypothesis whether the coefficients of the additional predictors equal 0. It does so comparing the deviance statistic and determining if changes in the deviance are statistically significant. Note that a major limitation of the deviance test is that it is for nested models i.e. a model being compared must be nested in the other. Below we compare our two models. The results indicate that adding an individual-level predictor (i.e. the share of population with no qualification) provides a model with better.\n\nanova(model6, model7)\n\nrefitting model(s) with ML (instead of REML)\n\n\nData: oa_shp\nModels:\nmodel6: unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\nmodel7: unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\n npar AIC BIC logLik deviance Chisq Df Pr(>Chisq) \nmodel6 6 -4764.7 -4732.5 2388.3 -4776.7 \nmodel7 7 -4956.5 -4918.9 2485.2 -4970.5 193.76 1 < 2.2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1" + "section": "\n8.6 Model building", + "text": "8.6 Model building\nNow we know how to estimate multilevel regression models in R. The question that remains is: When does multilevel modeling make a difference? The short answer is: when there is little group-level variation. When there is very little group-level variation, the multilevel modelling reduces to classical linear regression estimates with no group indicators. Inversely, when group-level coefficients vary greatly (compared to their standard errors of estimation), multilevel modelling reduces to classical regression with group indicators Gelman and Hill (2006).\nHow do you go about building a model?\nWe generally start simple by fitting simple linear regressions and then work our way up to a full multilevel model - see Gelman and Hill (2006) p. 270.\nHow many groups are needed?\nAs an absolute minimum, more than two groups are required. With only one or two groups, a multilevel model reduces to a linear regression model.\nHow many observations per group?\nTwo observations per group is sufficient to fit a multilevel model.\n\n8.6.1 Model Comparison\nHow we assess different candidate models? We can use the function anova() and assess various statistics: The Akaike Information Criterion (AIC), the Bayesian Information Criterion (BIC), Loglik and Deviance. Generally, we look for lower scores for all these indicators. We can also refer to the Chisq statistic below. It tests the hypothesis of whether additional predictors improve model fit. Particularly it tests the Null Hypothesis whether the coefficients of the additional predictors equal 0. It does so comparing the deviance statistic and determining if changes in the deviance are statistically significant. Note that a major limitation of the deviance test is that it is for nested models i.e. a model being compared must be nested in the other. Below we compare our two models. The results indicate that adding an individual-level predictor (i.e. the share of population with no qualification) provides a model with better.\n\nanova(model6, model7)\n\nrefitting model(s) with ML (instead of REML)\n\n\nData: oa_shp\nModels:\nmodel6: unemp ~ lt_ill + (1 + lt_ill | msoa_cd)\nmodel7: unemp ~ z_lt_ill + z_no_qual + (1 + z_lt_ill | msoa_cd)\n npar AIC BIC logLik deviance Chisq Df Pr(>Chisq) \nmodel6 6 -4764.7 -4732.5 2388.3 -4776.7 \nmodel7 7 -4956.5 -4918.9 2485.2 -4970.5 193.76 1 < 2.2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { "objectID": "08-multilevel-02.html#questions", "href": "08-multilevel-02.html#questions", "title": "8  Multilevel Modelling - Part 2", - "section": "8.7 Questions", - "text": "8.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nFit a varying-slope model. Let one slope to vary by region. Think carefully your choice.\nFit a varying-intercept and varying-slope model.\nCompare the results for models fitted in 1 and 2. Which is better? Why?\n\nUse the same explanatory variables used for the Chapter 7 challenge, so you can compare the model results from this chapter.\nAnalyse and discuss:\n\nthe varying slope estimate(s) from your model(s) (to what extent does the relationship between your dependent and independent variables vary across groups / areas? are they statistically significantly different?).\ndifferences between your varying intercept and varying slope models.\n\n\n\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel Modelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04." + "section": "\n8.7 Questions", + "text": "8.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nFit a varying-slope model. Let one slope to vary by region. Think carefully your choice.\nFit a varying-intercept and varying-slope model.\nCompare the results for models fitted in 1 and 2. Which is better? Why?\n\nUse the same explanatory variables used for the Chapter 7 challenge, so you can compare the model results from this chapter.\nAnalyse and discuss:\n\nthe varying slope estimate(s) from your model(s) (to what extent does the relationship between your dependent and independent variables vary across groups / areas? are they statistically significantly different?).\ndifferences between your varying intercept and varying slope models.\n\n\n\n\n\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel Modelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04.", + "crumbs": [ + "8  Multilevel Modelling - Part 2" + ] }, { - "objectID": "09-gwr.html#dependencies", - "href": "09-gwr.html#dependencies", + "objectID": "09-gwr.html", + "href": "09-gwr.html", "title": "9  Geographically Weighted Regression", - "section": "9.1 Dependencies", - "text": "9.1 Dependencies\nThis chapter uses the following libraries:\n\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(RColorBrewer) \n# More colour palettes\nlibrary(viridis) # nice colour schemes\n# Fitting geographically weighted regression models\nlibrary(spgwr)\n# Obtain correlation coefficients\nlibrary(corrplot)\n# Exportable regression tables\nlibrary(jtools)\nlibrary(stargazer)\nlibrary(sjPlot)\n# Assess multicollinearity\nlibrary(car)" + "section": "", + "text": "9.1 Dependencies\nThis chapter uses the following libraries:\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Colour palettes\nlibrary(RColorBrewer) \n# More colour palettes\nlibrary(viridis) # nice colour schemes\n# Fitting geographically weighted regression models\nlibrary(spgwr)\n# Obtain correlation coefficients\nlibrary(corrplot)\n# Exportable regression tables\nlibrary(jtools)\n# Assess multicollinearity\nlibrary(car)", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#data", "href": "09-gwr.html#data", "title": "9  Geographically Weighted Regression", - "section": "9.2 Data", - "text": "9.2 Data\nFor this chapter, we will use data on:\n\ncumulative COVID-19 confirmed cases from 1st January, 2020 to 14th April, 2020 from Public Health England via the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government.\n\nThe data used for this Chapter are organised at the ONS Upper Tier Local Authority (UTLA) level - also known as Counties and Unitary Authorities. They are the geographical units used to report COVID-19 data.\nIf you use the dataset utilised in this chapter, make sure cite this book. For a full list of the variables included in the data set used in this Chapter, see the readme file in the gwr data folder.1\nLet’s read the data:\n\n# clean workspace\nrm(list=ls())\n# read data\nutla_shp <- st_read(\"data/gwr/Covid19_total_cases_geo.shp\") %>%\n select(objct, cty19c, ctyu19nm, long, lat, st_rs, st_ln, X2020.04.14, I.PL1, IMD20, IMD2., Rsdnt, Hshld, Dwlln, Hsh_S, E_16_, A_65_, Ag_85, Mixed, Indin, Pkstn, Bngld, Chins, Oth_A, Black, Othr_t, CB_U_, Crwd_, Lng__, Trn__, Adm__, Ac___, Pb___, Edctn, H____, geometry)\n\n# replace nas with 0s\nutla_shp[is.na(utla_shp)] <- 0\n# explore data\nstr(utla_shp)" + "section": "\n9.2 Data", + "text": "9.2 Data\nFor this chapter, we will use data on:\n\ncumulative COVID-19 confirmed cases from 1st January, 2020 to 14th April, 2020 from Public Health England via the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government.\n\nThe data used for this Chapter are organised at the ONS Upper Tier Local Authority (UTLA) level - also known as Counties and Unitary Authorities. They are the geographical units used to report COVID-19 data.\nIf you use the dataset utilised in this chapter, make sure cite this book. For a full list of the variables included in the data set used in this Chapter, see the readme file in the gwr data folder.1\n1 Read the file in R by executing read_tsv(\"data/gwr/readme.txt\"). Ensure the library readr is installed before running read_tsv.99079Let’s read the data:\n\n# clean workspace\nrm(list=ls())\n# read data\nutla_shp <- st_read(\"data/gwr/Covid19_total_cases_geo.shp\") %>%\n select(objct, cty19c, ctyu19nm, long, lat, st_rs, st_ln, X2020.04.14, I.PL1, IMD20, IMD2., Rsdnt, Hshld, Dwlln, Hsh_S, E_16_, A_65_, Ag_85, Mixed, Indin, Pkstn, Bngld, Chins, Oth_A, Black, Othr_t, CB_U_, Crwd_, Lng__, Trn__, Adm__, Ac___, Pb___, Edctn, H____, geometry)\n\n# replace nas with 0s\nutla_shp[is.na(utla_shp)] <- 0\n# explore data\nstr(utla_shp)", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#recap-spatial-effects", "href": "09-gwr.html#recap-spatial-effects", "title": "9  Geographically Weighted Regression", - "section": "9.3 Recap: Spatial Effects", - "text": "9.3 Recap: Spatial Effects\nTo this point, we have implicitly discussed three distinctive spatial effects:\n\nSpatial heterogeneity refers to the uneven distribution of a variable’s values across space\nSpatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that they are more similar (or less similar) than expected for randomly associated pairs of observations\nSpatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space\n\nIn previous sessions, we considered multilevel models to deal with spatial nonstationarity, recognising that the strength and direction of the relationship between an outcome \\(y\\) and a set of predictors \\(x\\) may vary over space. Here we consider a different approach, namely geographically weighted regression (GWR)." + "section": "\n9.3 Recap: Spatial Effects", + "text": "9.3 Recap: Spatial Effects\nTo this point, we have implicitly discussed three distinctive spatial effects:\n\nSpatial heterogeneity refers to the uneven distribution of a variable’s values across space\nSpatial dependence refers to the spatial relationship of a variable’s values for a pair of locations at a certain distance apart, so that they are more similar (or less similar) than expected for randomly associated pairs of observations\nSpatial nonstationarity refers to variations in the relationship between an outcome variable and a set of predictor variables across space\n\nIn previous sessions, we considered multilevel models to deal with spatial nonstationarity, recognising that the strength and direction of the relationship between an outcome \\(y\\) and a set of predictors \\(x\\) may vary over space. Here we consider a different approach, namely geographically weighted regression (GWR).", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#exploratory-analysis", "href": "09-gwr.html#exploratory-analysis", "title": "9  Geographically Weighted Regression", - "section": "9.4 Exploratory Analysis", - "text": "9.4 Exploratory Analysis\nWe will explore this technique through an empirical analysis considering the current global COVID-19 outbreak. Specifically we will seek to identify potential contextual factors that may be related to an increased risk of local infection. Population density, overcrowded housing, vulnerable individuals and critical workers have all been linked to a higher risk of COVID-19 infection.\nFirst, we will define and develop some basic understanding of our variable of interest. We define the risk of COVID-19 infection by the cumulative number of confirmed positive cases COVID-19 per 100,000 people:\n\n# risk of covid-19 infection\nutla_shp$covid19_r <- (utla_shp$X2020.04.14 / utla_shp$Rsdnt) * 100000\n\n# histogram\nggplot(data = utla_shp) +\ngeom_density(alpha=0.8, colour=\"black\", fill=\"lightblue\", aes(x = covid19_r)) +\n theme_classic()\n\n\n\n# distribution in numbers\nsummary(utla_shp$covid19_r)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n 32.11 92.81 140.15 146.66 190.96 341.56 \n\n\nThe results indicate a wide variation in the risk of infection across UTLAs in England, ranging from 31 to 342 confirmed positive cases of COVID-19 per 100,000 people with a median of 147. We map the cases to understand their spatial structure.\n\n# read region boundaries for a better looking map\nreg_shp <- st_read(\"data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp\")\n\nReading layer `Regions_December_2019_Boundaries_EN_BGC' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 9 features and 9 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 82672 ymin: 5342.7 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n# ensure geometry is valid\nutla_shp = sf::st_make_valid(utla_shp)\nreg_shp = sf::st_make_valid(reg_shp)\n\n# map\nlegend_title = expression(\"Cumulative cases per 100,000\")\nmap_utla = tm_shape(utla_shp) +\n tm_fill(col = \"covid19_r\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_utla + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\nThe map shows that concentrations of high incidence of infections in the metropolitan areas of London, Liverpool, Newcastle, Sheffield, Middlesbrough and Birmingham. Below we list the UTLAs in these areas in descending order.\n\nhotspots <- utla_shp %>% select(ctyu19nm, covid19_r) %>%\n filter(covid19_r > 190)\nhotspots[order(-hotspots$covid19_r),]\n\nSimple feature collection with 38 features and 2 fields\nGeometry type: GEOMETRY\nDimension: XY\nBounding box: xmin: 293941.4 ymin: 155850.8 xmax: 561956.7 ymax: 588517.4\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n ctyu19nm covid19_r geometry\n14 Brent 341.5645 POLYGON ((520113.1 190480.8...\n32 Southwark 337.1687 POLYGON ((532223 180545.4, ...\n27 Lambeth 305.5238 POLYGON ((531189.5 180531.3...\n22 Harrow 286.1254 POLYGON ((517363.8 194171.3...\n17 Croydon 276.8467 POLYGON ((531549.3 171045, ...\n12 Barnet 274.1410 POLYGON ((524645.2 198138.3...\n28 Lewisham 261.3408 POLYGON ((536691.6 178958.8...\n30 Newham 258.4550 POLYGON ((542600.7 186497.3...\n38 Cumbria 255.6726 MULTIPOLYGON (((321364.8 46...\n15 Bromley 253.7234 POLYGON ((542252.7 172828.7...\n\n\n\nChallenge 1: How does Liverpool ranked in this list?" + "section": "\n9.4 Exploratory Analysis", + "text": "9.4 Exploratory Analysis\nWe will explore this technique through an empirical analysis considering the current global COVID-19 outbreak. Specifically we will seek to identify potential contextual factors that may be related to an increased risk of local infection. Population density, overcrowded housing, vulnerable individuals and critical workers have all been linked to a higher risk of COVID-19 infection.\nFirst, we will define and develop some basic understanding of our variable of interest. We define the risk of COVID-19 infection by the cumulative number of confirmed positive cases COVID-19 per 100,000 people:\n\n# risk of covid-19 infection\nutla_shp$covid19_r <- (utla_shp$X2020.04.14 / utla_shp$Rsdnt) * 100000\n\n# histogram\nggplot(data = utla_shp) +\ngeom_density(alpha=0.8, colour=\"black\", fill=\"lightblue\", aes(x = covid19_r)) +\n theme_classic()\n\n\n\n\n\n\n# distribution in numbers\nsummary(utla_shp$covid19_r)\n\n Min. 1st Qu. Median Mean 3rd Qu. Max. \n 32.11 92.81 140.15 146.66 190.96 341.56 \n\n\nThe results indicate a wide variation in the risk of infection across UTLAs in England, ranging from 31 to 342 confirmed positive cases of COVID-19 per 100,000 people with a median of 147. We map the cases to understand their spatial structure.\n\n# read region boundaries for a better looking map\nreg_shp <- st_read(\"data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp\")\n\nReading layer `Regions_December_2019_Boundaries_EN_BGC' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/gwr/Regions_December_2019_Boundaries_EN_BGC.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 9 features and 9 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 82672 ymin: 5342.7 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n# ensure geometry is valid\nutla_shp = sf::st_make_valid(utla_shp)\nreg_shp = sf::st_make_valid(reg_shp)\n\n# map\nlegend_title = expression(\"Cumulative cases per 100,000\")\nmap_utla = tm_shape(utla_shp) +\n tm_fill(col = \"covid19_r\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_utla + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\n\n\n\nThe map shows that concentrations of high incidence of infections in the metropolitan areas of London, Liverpool, Newcastle, Sheffield, Middlesbrough and Birmingham. Below we list the UTLAs in these areas in descending order.\n\nhotspots <- utla_shp %>% select(ctyu19nm, covid19_r) %>%\n filter(covid19_r > 190)\nhotspots[order(-hotspots$covid19_r),]\n\nSimple feature collection with 38 features and 2 fields\nGeometry type: GEOMETRY\nDimension: XY\nBounding box: xmin: 293941.4 ymin: 155850.8 xmax: 561956.7 ymax: 588517.4\nProjected CRS: Transverse_Mercator\nFirst 10 features:\n ctyu19nm covid19_r geometry\n14 Brent 341.5645 POLYGON ((520113.1 190480.8...\n32 Southwark 337.1687 POLYGON ((532223 180545.4, ...\n27 Lambeth 305.5238 POLYGON ((531189.5 180531.3...\n22 Harrow 286.1254 POLYGON ((517363.8 194171.3...\n17 Croydon 276.8467 POLYGON ((531549.3 171045, ...\n12 Barnet 274.1410 POLYGON ((524645.2 198138.3...\n28 Lewisham 261.3408 POLYGON ((536691.6 178958.8...\n30 Newham 258.4550 POLYGON ((542600.7 186497.3...\n38 Cumbria 255.6726 MULTIPOLYGON (((321364.8 46...\n15 Bromley 253.7234 POLYGON ((542252.7 172828.7...\n\n\n\nChallenge 1: How does Liverpool ranked in this list?", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#global-regression", "href": "09-gwr.html#global-regression", "title": "9  Geographically Weighted Regression", - "section": "9.5 Global Regression", - "text": "9.5 Global Regression\nTo provide an intuitive understanding of GWR, a useful start is to explore the data using an ordinary least squares (OLS) linear regression model. The key issue here is to understand if high incidence of COVID-19 is linked to structural differences across UTLAs in England. As indicated above, confirmed positive cases of COVID-19 have been associated with overcrowded housing, vulnerable populations - including people in elderly age groups, economically disadvantaged groups and those suffering from chronic health conditions - ethnic minorities, critical workers in the health & social work, education, accommodation & food, transport, and administrative & support sectors. So, let’s create a set of variables to approximate these factors.\n\n# define predictors\nutla_shp <- utla_shp %>% mutate(\n crowded_hou = Crwd_ / Hshld, # share of crowded housing\n elderly = (A_65_ + Ag_85) / Rsdnt, # share of population aged 65+\n lt_illness = Lng__ / Rsdnt, # share of population in long-term illness\n ethnic = (Mixed + Indin + Pkstn + Bngld + Chins + Oth_A + Black + Othr_t) / Rsdnt, # share of nonwhite population\n imd19_ext = IMD20, # proportion of a larger area’s population living in the most deprived LSOAs in the country\n hlthsoc_sec = H____ / E_16_, # share of workforce in the human health & social work sector\n educ_sec = Edctn / E_16_, # share of workforce in the education sector\n trnsp_sec= Trn__ / E_16_, # share of workforce in the Transport & storage sector\n accfood_sec = Ac___ / E_16_, # share of workforce in the accommodation & food service sector\n admsupport_sec = Adm__ / E_16_, # share of workforce in the administrative & support sector\n pblic_sec = Pb___ / E_16_ # share of workforce in the public administration & defence sector\n)\n\nLet’s quickly examine how they correlate to our outcome variable i.e. incidence rate of COVID-19 using correlation coefficients and correlograms.\n\n# obtain a matrix of Pearson correlation coefficients\ndf_sel <- st_set_geometry(utla_shp[,37:48], NULL) # temporary data set removing geometries\ncormat <- cor(df_sel, use=\"complete.obs\", method=\"pearson\")\n\n# significance test\nsig1 <- corrplot::cor.mtest(df_sel, conf.level = .95)\n\n# creta a correlogram\ncorrplot::corrplot(cormat, type=\"lower\",\n method = \"circle\", \n order = \"original\", \n tl.cex = 0.7,\n p.mat = sig1$p, sig.level = .05, \n col = viridis::viridis(100, option = \"plasma\"),\n diag = FALSE)\n\n\n\n\nThe correlogram shows the strength and significance of the linear relationship between our set of variables. The size of the circle reflects the strength of the relationships as captured by the Pearson correlation coefficient, and crosses indicate statistically insignificant relationships at the 95% level of confidence. The colour indicate the direction of the relationship with dark (light) colours indicating a negative (positive) association.\nThe results indicate that the incidence of COVID-19 is significantly and positively related to the share of overcrowded housing, nonwhite ethnic minorities and administrative & support workers. Against expectations, the incidence of COVID-19 appears to be negatively correlated with the share of elderly population, of population suffering from long-term illness and of administrative & support workers, and displays no significant association with the share of the population living in deprived areas as well as the share of public administration & defence workers, and health & social workers. The latter probably reflects the effectiveness of the protective measures undertaken to prevent infection among these population groups, but it may also reflect the partial coverage of COVID-19 testing and underreporting. It may also reveal the descriptive limitations of correlation coefficients as they show the relationship between a pairs of variables, not controlling for others. Correlation coefficients can thus produce spurious relationships resulting from confounded variables. We will return to this point below.\nThe results also reveal high collinearity between particular pairs of variables, notably between the share of crowded housing and of nonwhite ethnic population, the share of crowded housing and of elderly population, the share of overcrowded housing and of administrative & support workers, the share of elderly population and of population suffering from long-term illness. A more refined analysis of multicollinearity is needed. Various diagnostics for multicollinearity in a regression framework exist, including matrix condition numbers (CNs), predictor variance inflation factors (VIFs) and variance decomposition factors (VDPs). Rules of thumb (CNs > 30, VIFs > 10 and VDPs > 0.5) to indicate worrying levels of collinearity can be found in Belsley, Kuh, and Welsch (2005). To avoid problems of multicollinearity, often a simple strategy is to remove highly correlated predictors. The difficultly is in deciding which predictor(s) to remove, especially when all are considered important. Keep this in mind when specifying your model.\n\nChallenge 2: Analyse the relationship of all the variables executing pairs(df_sel). How accurate would a linear regression be in capturing the relationships for our set of variables?\n\n\n9.5.1 Global Regression Results\nTo gain a better understanding of these relationships, we can regress the incidence rate of COVID-19 on a series of factors capturing differences across areas. To focus on the description of GWR, we keep our analysis simple and study the incidence rate of COVID-19 as a function of the share of nonwhite ethnic population and of population suffering from long-term illness by estimating the following OLS linear regression model:\n\n# attach data\nattach(utla_shp)\n\n# specify a model equation\neq1 <- covid19_r ~ ethnic + lt_illness\nmodel1 <- lm(formula = eq1, data = utla_shp)\n\n# estimates\nsummary(model1)\n\n\nCall:\nlm(formula = eq1, data = utla_shp)\n\nResiduals:\n Min 1Q Median 3Q Max \n-109.234 -38.386 -4.879 29.284 143.786 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 63.77 30.13 2.117 0.036 * \nethnic 271.10 30.65 8.845 2.64e-15 ***\nlt_illness 216.20 151.88 1.424 0.157 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 51 on 147 degrees of freedom\nMultiple R-squared: 0.3926, Adjusted R-squared: 0.3844 \nF-statistic: 47.52 on 2 and 147 DF, p-value: < 2.2e-16\n\n\nWe also compute the VIFs for the variables in the model:\n\nvif(model1)\n\n ethnic lt_illness \n 1.43015 1.43015 \n\n\nThe regression results indicate a positive relationship exists between the share of nonwhite population and an increased risk of COVID-19 infection. A one percentage point increase in the share of nonwhite population returns a 271 rise in the cumulative count of COVID-19 infection per 100,000 people, everything else constant. The results also reveal a positive (albeit statistically insignificant) relationship between the share of population suffering from long-term illness and an increased risk of COVID-19 infection, after controlling for the share of nonwhite population, thereby confirming our suspicion about the limitations of correlation coefficients; that is, once differences in the share of nonwhite population are taken into account, the association between the share of population suffering from long-term illness and an increased risk of COVID-19 infection becomes positive. We also test for multicollinearity. The VIFs are below 10 indicating that multicollinearity is not highly problematic.\nThe \\(R^{2}\\) value for the OLS regression is 0.393 indicating that our model explains only 39% of the variance in the rate of COVID-19 infection. This leaves 71% of the variance unexplained. Some of this unexplained variance can be because we have only included two explanatory variables in our model, but also because the OLS regression model assumes that the relationships in the model are constant over space; that is, it assumes a stationary process. Hence, an OLS regression model is considered to capture global relationships. However, relationships may vary over space. Suppose, for instance, that there are intrinsic behavioural variations across England and that people have adhered more strictly to self-isolation and social distancing measures in some areas than in others, or that ethnic minorities are less exposed to contracting COVID-19 in certain parts of England. If such variations in associations exist over space, our estimated OLS model will be a misspecification of reality because it assumes these relationships to be constant.\nTo better understand this potential misspecification, we investigate the model residuals which show high variability (see below). The distribution is non-random displaying large positive residuals in the metropolitan areas of London, Liverpool, Newcastle (in light colours) and the Lake District and large negative residuals across much of England (in black). This conforms to the spatial pattern of confirmed COVID-19 cases with high concentration in a limited number of metropolitan areas (see above). While our residual map reveals that there is a problem with the OLS model, it does not indicate which, if any, of the parameters in the model might exhibit spatial nonstationarity. A simple way of examining if the relationships being modelled in our global OLS model are likely to be stationary over space would be to estimate separate OLS model for each UTLA in England. But this would require higher resolution i.e. data within UTLA, and we only have one data point per UTLA. -Fotheringham, Brunsdon, and Charlton (2002) (2002, p.40-44) discuss alternative approaches and their limitations.\n\nutla_shp$res_m1 <- residuals(model1)\n\n# map\nlegend_title = expression(\"OLS residuals\")\nmap_utla = tm_shape(utla_shp) +\n tm_fill(col = \"res_m1\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_utla + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders" + "section": "\n9.5 Global Regression", + "text": "9.5 Global Regression\nTo provide an intuitive understanding of GWR, a useful start is to explore the data using an ordinary least squares (OLS) linear regression model. The key issue here is to understand if high incidence of COVID-19 is linked to structural differences across UTLAs in England. As indicated above, confirmed positive cases of COVID-19 have been associated with overcrowded housing, vulnerable populations - including people in elderly age groups, economically disadvantaged groups and those suffering from chronic health conditions - ethnic minorities, critical workers in the health & social work, education, accommodation & food, transport, and administrative & support sectors. So, let’s create a set of variables to approximate these factors.\n\n# define predictors\nutla_shp <- utla_shp %>% mutate(\n crowded_hou = Crwd_ / Hshld, # share of crowded housing\n elderly = (A_65_ + Ag_85) / Rsdnt, # share of population aged 65+\n lt_illness = Lng__ / Rsdnt, # share of population in long-term illness\n ethnic = (Mixed + Indin + Pkstn + Bngld + Chins + Oth_A + Black + Othr_t) / Rsdnt, # share of nonwhite population\n imd19_ext = IMD20, # proportion of a larger area’s population living in the most deprived LSOAs in the country\n hlthsoc_sec = H____ / E_16_, # share of workforce in the human health & social work sector\n educ_sec = Edctn / E_16_, # share of workforce in the education sector\n trnsp_sec= Trn__ / E_16_, # share of workforce in the Transport & storage sector\n accfood_sec = Ac___ / E_16_, # share of workforce in the accommodation & food service sector\n admsupport_sec = Adm__ / E_16_, # share of workforce in the administrative & support sector\n pblic_sec = Pb___ / E_16_ # share of workforce in the public administration & defence sector\n)\n\nLet’s quickly examine how they correlate to our outcome variable i.e. incidence rate of COVID-19 using correlation coefficients and correlograms.\n\n# obtain a matrix of Pearson correlation coefficients\ndf_sel <- st_set_geometry(utla_shp[,37:48], NULL) # temporary data set removing geometries\ncormat <- cor(df_sel, use=\"complete.obs\", method=\"pearson\")\n\n# significance test\nsig1 <- corrplot::cor.mtest(df_sel, conf.level = .95)\n\n# creta a correlogram\ncorrplot::corrplot(cormat, type=\"lower\",\n method = \"circle\", \n order = \"original\", \n tl.cex = 0.7,\n p.mat = sig1$p, sig.level = .05, \n col = viridis::viridis(100, option = \"plasma\"),\n diag = FALSE)\n\n\n\n\n\n\n\nThe correlogram shows the strength and significance of the linear relationship between our set of variables. The size of the circle reflects the strength of the relationships as captured by the Pearson correlation coefficient, and crosses indicate statistically insignificant relationships at the 95% level of confidence. The colour indicate the direction of the relationship with dark (light) colours indicating a negative (positive) association.\nThe results indicate that the incidence of COVID-19 is significantly and positively related to the share of overcrowded housing, nonwhite ethnic minorities and administrative & support workers. Against expectations, the incidence of COVID-19 appears to be negatively correlated with the share of elderly population, of population suffering from long-term illness and of administrative & support workers, and displays no significant association with the share of the population living in deprived areas as well as the share of public administration & defence workers, and health & social workers. The latter probably reflects the effectiveness of the protective measures undertaken to prevent infection among these population groups, but it may also reflect the partial coverage of COVID-19 testing and underreporting. It may also reveal the descriptive limitations of correlation coefficients as they show the relationship between a pairs of variables, not controlling for others. Correlation coefficients can thus produce spurious relationships resulting from confounded variables. We will return to this point below.\nThe results also reveal high collinearity between particular pairs of variables, notably between the share of crowded housing and of nonwhite ethnic population, the share of crowded housing and of elderly population, the share of overcrowded housing and of administrative & support workers, the share of elderly population and of population suffering from long-term illness. A more refined analysis of multicollinearity is needed. Various diagnostics for multicollinearity in a regression framework exist, including matrix condition numbers (CNs), predictor variance inflation factors (VIFs) and variance decomposition factors (VDPs). Rules of thumb (CNs > 30, VIFs > 10 and VDPs > 0.5) to indicate worrying levels of collinearity can be found in Belsley, Kuh, and Welsch (2005). To avoid problems of multicollinearity, often a simple strategy is to remove highly correlated predictors. The difficultly is in deciding which predictor(s) to remove, especially when all are considered important. Keep this in mind when specifying your model.\n\nChallenge 2: Analyse the relationship of all the variables executing pairs(df_sel). How accurate would a linear regression be in capturing the relationships for our set of variables?\n\n\n9.5.1 Global Regression Results\nTo gain a better understanding of these relationships, we can regress the incidence rate of COVID-19 on a series of factors capturing differences across areas. To focus on the description of GWR, we keep our analysis simple and study the incidence rate of COVID-19 as a function of the share of nonwhite ethnic population and of population suffering from long-term illness by estimating the following OLS linear regression model:\n\n# attach data\nattach(utla_shp)\n\n# specify a model equation\neq1 <- covid19_r ~ ethnic + lt_illness\nmodel1 <- lm(formula = eq1, data = utla_shp)\n\n# estimates\nsummary(model1)\n\n\nCall:\nlm(formula = eq1, data = utla_shp)\n\nResiduals:\n Min 1Q Median 3Q Max \n-109.234 -38.386 -4.879 29.284 143.786 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) 63.77 30.13 2.117 0.036 * \nethnic 271.10 30.65 8.845 2.64e-15 ***\nlt_illness 216.20 151.88 1.424 0.157 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 51 on 147 degrees of freedom\nMultiple R-squared: 0.3926, Adjusted R-squared: 0.3844 \nF-statistic: 47.52 on 2 and 147 DF, p-value: < 2.2e-16\n\n\nWe also compute the VIFs for the variables in the model:\n\nvif(model1)\n\n ethnic lt_illness \n 1.43015 1.43015 \n\n\nThe regression results indicate a positive relationship exists between the share of nonwhite population and an increased risk of COVID-19 infection. A one percentage point increase in the share of nonwhite population returns a 271 rise in the cumulative count of COVID-19 infection per 100,000 people, everything else constant. The results also reveal a positive (albeit statistically insignificant) relationship between the share of population suffering from long-term illness and an increased risk of COVID-19 infection, after controlling for the share of nonwhite population, thereby confirming our suspicion about the limitations of correlation coefficients; that is, once differences in the share of nonwhite population are taken into account, the association between the share of population suffering from long-term illness and an increased risk of COVID-19 infection becomes positive. We also test for multicollinearity. The VIFs are below 10 indicating that multicollinearity is not highly problematic.\nThe \\(R^{2}\\) value for the OLS regression is 0.393 indicating that our model explains only 39% of the variance in the rate of COVID-19 infection. This leaves 71% of the variance unexplained. Some of this unexplained variance can be because we have only included two explanatory variables in our model, but also because the OLS regression model assumes that the relationships in the model are constant over space; that is, it assumes a stationary process. Hence, an OLS regression model is considered to capture global relationships. However, relationships may vary over space. Suppose, for instance, that there are intrinsic behavioural variations across England and that people have adhered more strictly to self-isolation and social distancing measures in some areas than in others, or that ethnic minorities are less exposed to contracting COVID-19 in certain parts of England. If such variations in associations exist over space, our estimated OLS model will be a misspecification of reality because it assumes these relationships to be constant.\nTo better understand this potential misspecification, we investigate the model residuals which show high variability (see below). The distribution is non-random displaying large positive residuals in the metropolitan areas of London, Liverpool, Newcastle (in light colours) and the Lake District and large negative residuals across much of England (in black). This conforms to the spatial pattern of confirmed COVID-19 cases with high concentration in a limited number of metropolitan areas (see above). While our residual map reveals that there is a problem with the OLS model, it does not indicate which, if any, of the parameters in the model might exhibit spatial nonstationarity. A simple way of examining if the relationships being modelled in our global OLS model are likely to be stationary over space would be to estimate separate OLS model for each UTLA in England. But this would require higher resolution i.e. data within UTLA, and we only have one data point per UTLA. -Fotheringham, Brunsdon, and Charlton (2002) (2002, p.40-44) discuss alternative approaches and their limitations.\n\nutla_shp$res_m1 <- residuals(model1)\n\n# map\nlegend_title = expression(\"OLS residuals\")\nmap_utla = tm_shape(utla_shp) +\n tm_fill(col = \"res_m1\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_utla + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#fitting-a-geographically-weighted-regression", "href": "09-gwr.html#fitting-a-geographically-weighted-regression", "title": "9  Geographically Weighted Regression", - "section": "9.6 Fitting a Geographically Weighted Regression", - "text": "9.6 Fitting a Geographically Weighted Regression\nGWR overcomes the limitation of the OLS regression model of generating a global set of estimates. The basic idea behind GWR is to examine the way in which the relationships between a dependent variable and a set of predictors might vary over space. GWR operates by moving a search window from one regression point to the next, working sequentially through all the existing regression points in the dataset. A set of regions is then defined around each regression point and within the search window. A regression model is then fitted to all data contained in each of the identified regions around a regression point, with data points closer to the sample point being weighted more heavily than are those farther away. This process is repeated for all samples points in the dataset. For a data set of 150 observations GWR will fit 150 weighted regression models. The resulting local estimates can then be mapped at the locations of the regression points to view possible variations in the relationships between variables.\nGraphically, GWR involves fitting a spatial kernel to the data as described in the Fig. 1. For a given regression point \\(X\\), the weight (\\(W\\)) of a data point is at a maximum at the location of the regression point. The weight decreases gradually as the distance between two points increases. A regression model is thus calibrated locally by moving the regression point across the area under study. For each location, the data are weighted differently so that the resulting estimates are unique to a particular location.\n\n\n\nFig. 1. GWR with fixed spatial kernel. Source: Fotheringham et al. (2002, 45).\n\n\n\n9.6.1 Fixed or Adaptive Kernel\nA key issue is to decide between two options of spatial kernels: a fixed kernel or an adaptive kernel. Intuitively, a fixed kernel involves using a fixed bandwidth to define a region around all regression points as displayed in Fig. 1. The extent of the kernel is determined by the distance to a given regression point, with the kernel being identical at any point in space. An adaptive kernel involves using varying bandwidth to define a region around regression points as displayed in Fig. 2. The extent of the kernel is determined by the number of nearest neighbours from a given regression point. The kernels have larger bandwidths where the data are sparse.\n\n\n\nFig. 2. GWR with adaptive spatial kernel. Source: Fotheringham et al. (2002, 47).\n\n\n\n\n9.6.2 Optimal Bandwidth\nA second issue is to define the extent of geographical area (i.e. optimal bandwidth) of the spatial kernel. The bandwidth is the distance beyond which a value of zero is assigned to weight observations. Larger bandwidths include a larger number of observations receiving a non-zero weight and more observations are used to fit a local regression.\nTo determine the optimal bandwidth, a cross-validation approach is applied; that is, for a location, a local regression is fitted based on a given bandwidth and used to predict the value of the dependent variable. The resulting predicted value is used to compute the residuals of the model. Residuals are compared using a series of bandwidth and the bandwidth returning the smallest local residuals are selected.\nVariance and Bias Trade off\nChoosing an optimal bandwidth involves a compromise between bias and precision. For example, a larger bandwidth will involve using a larger number of observations to fit a local regression, and hence result in reduced variance (or increased precision) but high bias of estimates. On the other hand, too small bandwidth involves using a very small number of observations resulting in increased variance but small bias. An optimal bandwidth offers a compromise between bias and variance.\n\n\n9.6.3 Shape of Spatial Kernel\nTwo general set of kernel functions can be distinguished: continuous kernels and kernels with compact support. Continuous kernels are used to weight all observations in the study area and includes uniform, Gaussian and Exponential kernel functions. Kernel with compact support are used to assign a nonzero weight to observations within a certain distance and a zero weight beyond it. The shape of the kernel has been reported to cause small changes to resulting estimates (Brunsdon, Fotheringham, and Charlton 1998).\n\n\n9.6.4 Selecting a Bandwidth\nLet’s now implement a GWR model. The first key step is to define the optimal bandwidth. We first illustrate the use of a fixed spatial kernel.\n\n9.6.4.1 Fixed Bandwidth\nCross-validation is used to search for the optimal bandwidth. Recall that this procedure compares the model residuals based on different bandwidths and chooses the optimal solution i.e. the bandwidth returning the smallest model residuals based on a given model specification. A key parameter here is the shape of the geographical weight function (gweight). We set it to be a Gaussian function which is the default. A bi-square function is recommended to reduce computational time. Since we have a simple model, a Gaussian function should not take that long. Note that we set the argument longlat to TRUE and use latitude and longitude for coordinates (coords). When longlat is set to TRUE, distances are measured in kilometres.\n\n# find optimal kernel bandwidth using cross validation\nfbw <- gwr.sel(eq1, \n data = utla_shp, \n coords=cbind( long, lat),\n longlat = TRUE,\n adapt=FALSE, \n gweight = gwr.Gauss, \n verbose = FALSE)\n\n# view selected bandwidth\nfbw\n\n[1] 29.30417\n\n\nThe result indicates that the optimal bandwidth is 39.79 kms. This means that neighbouring UTLAs within a fixed radius of 39.79 kms will be taken to estimate local regressions. To estimate a GWR, we execute the code below in which the optimal bandwidth above is used as an input in the argument bandwidth.\n\n# fit a gwr based on fixed bandwidth\nfb_gwr <- gwr(eq1, \n data = utla_shp,\n coords=cbind( long, lat),\n longlat = TRUE,\n bandwidth = fbw, \n gweight = gwr.Gauss,\n hatmatrix=TRUE, \n se.fit=TRUE)\n\nfb_gwr\n\nCall:\ngwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), \n bandwidth = fbw, gweight = gwr.Gauss, hatmatrix = TRUE, longlat = TRUE, \n se.fit = TRUE)\nKernel function: gwr.Gauss \nFixed bandwidth: 29.30417 \nSummary of GWR coefficient estimates at data points:\n Min. 1st Qu. Median 3rd Qu. Max. Global\nX.Intercept. -187.913 -42.890 93.702 211.685 792.989 63.768\nethnic -785.938 104.813 194.609 254.717 1078.854 271.096\nlt_illness -2599.119 -563.128 128.176 690.603 1507.024 216.198\nNumber of data points: 150 \nEffective number of parameters (residual: 2traceS - traceS'S): 57.11019 \nEffective degrees of freedom (residual: 2traceS - traceS'S): 92.88981 \nSigma (residual: 2traceS - traceS'S): 38.34777 \nEffective number of parameters (model: traceS): 44.65744 \nEffective degrees of freedom (model: traceS): 105.3426 \nSigma (model: traceS): 36.00992 \nSigma (ML): 30.17717 \nAICc (GWR p. 61, eq 2.33; p. 96, eq. 4.21): 1580.349 \nAIC (GWR p. 96, eq. 4.22): 1492.465 \nResidual sum of squares: 136599.2 \nQuasi-global R2: 0.7830537 \n\n\nWe will skip the interpretation of the results for now and consider them in the next section. Now, we want to focus on the overall model fit and will map the results of the \\(R^{2}\\) for the estimated local regressions. To do this, we extract the model results stored in a Spatial Data Frame (SDF) and add them to our spatial data frame utla_shp. Note that the Quasi-global \\(R^{2}\\) is very high (0.77) indicating a high in-sample prediction accuracy.\n\n# write gwr output into a data frame\nfb_gwr_out <- as.data.frame(fb_gwr$SDF)\n\nutla_shp$fmb_localR2 <- fb_gwr_out$localR2\n\n# map\n # Local R2\nlegend_title = expression(\"Fixed: Local R2\")\nmap_fbgwr1 = tm_shape(utla_shp) +\n tm_fill(col = \"fmb_localR2\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_fbgwr1 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\nThe map shows very high in-sample model predictions of up to 80% in relatively large UTLAs (i.e. Cornwall, Devon and Cumbria) but poor predictions in Linconshire and small UTLAs in the North West and Yorkshire & The Humber Regions and the Greater London. The spatial distribution of this pattern may reflect a potential problem that arise in the application of GWR with fixed spatial kernels. The use of fixed kernels implies that local regressions for small spatial units may be calibrated on a large number of dissimilar areas, while local regressions for large areas may be calibrated on very few data points, giving rise to estimates with large standard errors. In extreme cases, generating estimates might not be possible due to insufficient variation in small samples. In practice, this issue is relatively common if the number of geographical areas in the dataset is small.\n\n\n9.6.4.2 Adaptive Bandwidth\nTo reduce these problems, adaptive spatial kernels can be used. These kernels adapt in size to variations in the density of the data so that the kernels have larger bandwidths where the data are sparse and have smaller bandwidths where the data are plentiful. As above, we first need to search for the optimal bandwidth before estimating a GWR.\n\n# find optimal kernel bandwidth using cross validation\nabw <- gwr.sel(eq1, \n data = utla_shp, \n coords=cbind( long, lat),\n longlat = TRUE,\n adapt = TRUE, \n gweight = gwr.Gauss, \n verbose = FALSE)\n\n# view selected bandwidth\nabw\n\n[1] 0.03126972\n\n\nThe optimal bandwidth is 0.03 indicating the proportion of observations (or k-nearest neighbours) to be included in the weighting scheme. In this example, the optimal bandwidth indicates that for a given UTLA, 3% of its nearest neighbours should be used to calibrate the relevant local regression; that is about 5 UTLAs. The search window will thus be variable in size depending on the extent of UTLAs. Note that here the optimal bandwidth is defined based on a data point’s k-nearest neighbours. It can also be defined by geographical distance as done above for the fixed spatial kernel. We next fit a GWR based on an adaptive bandwidth.\n\n# fit a gwr based on adaptive bandwidth\nab_gwr <- gwr(eq1, \n data = utla_shp,\n coords=cbind( long, lat),\n longlat = TRUE,\n adapt = abw, \n gweight = gwr.Gauss,\n hatmatrix=TRUE, \n se.fit=TRUE)\n\nab_gwr\n\nCall:\ngwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), \n gweight = gwr.Gauss, adapt = abw, hatmatrix = TRUE, longlat = TRUE, \n se.fit = TRUE)\nKernel function: gwr.Gauss \nAdaptive quantile: 0.03126972 (about 4 of 150 data points)\nSummary of GWR coefficient estimates at data points:\n Min. 1st Qu. Median 3rd Qu. Max. Global\nX.Intercept. -198.790 -28.398 113.961 226.437 346.510 63.768\nethnic -121.872 106.822 229.591 283.739 1162.123 271.096\nlt_illness -1907.098 -746.468 -125.855 798.875 1496.549 216.198\nNumber of data points: 150 \nEffective number of parameters (residual: 2traceS - traceS'S): 48.59361 \nEffective degrees of freedom (residual: 2traceS - traceS'S): 101.4064 \nSigma (residual: 2traceS - traceS'S): 36.57493 \nEffective number of parameters (model: traceS): 36.04378 \nEffective degrees of freedom (model: traceS): 113.9562 \nSigma (model: traceS): 34.50222 \nSigma (ML): 30.07257 \nAICc (GWR p. 61, eq 2.33; p. 96, eq. 4.21): 1546.029 \nAIC (GWR p. 96, eq. 4.22): 1482.809 \nResidual sum of squares: 135653.9 \nQuasi-global R2: 0.7845551 \n\n\n\n\n\n9.6.5 Model fit\nAssessing the global fit of the model, marginal improvements are observed. The \\(AIC\\) and Residual sum of squares experienced marginal reductions, while the \\(R^{2}\\) increased compared to the GRW based on a fixed kernel. To gain a better understanding of these changes, as above, we map the \\(R^{2}\\) values for the estimated local regressions.\n\n# write gwr output into a data frame\nab_gwr_out <- as.data.frame(ab_gwr$SDF)\n\nutla_shp$amb_ethnic <- ab_gwr_out$ethnic\nutla_shp$amb_lt_illness <- ab_gwr_out$lt_illness\nutla_shp$amb_localR2 <- ab_gwr_out$localR2\n\n# map\n # Local R2\nlegend_title = expression(\"Adaptive: Local R2\")\nmap_abgwr1 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_localR2\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr1 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\nThe map reveals notable improvements in local estimates for UTLAs within West and East Midlands, the South East, South West and East of England. Estimates are still poor in hot spot UTLAs concentrating confirmed cases of COVID-19, such as the Greater London, Liverpool and Newcastle areas.\n\n\n9.6.6 Interpretation\nThe key strength of GWR models is in identifying patterns of spatial variation in the associations between pairs of variables. The results reveal how these coefficients vary across the 150 UTLAs of England. To examine this variability, let’s first focus on the adaptive GWR output reported in Section 8.6.4.2. The output includes a summary of GWR coefficient estimates at various data points. The last column reports the global estimates which are the same as the coefficients from the OLS regression we fitted at the start of our analysis. For our variable nonwhite ethnic population, the GWR outputs reveals that local coefficients range from a minimum value of -148.41 to a maximum value of 1076.84, indicating that one percentage point increase in the share of nonwhite ethnic population is associated with a a reduction of 148.41 in the number of cumulative confirmed cases of COVID-19 per 100,000 people in some UTLAs and an increase of 1076.84 in others. For half of the UTLAs in the dataset, as the share of nonwhite ethnic population increases by one percentage point, the rate of COVID-19 will increase between 106.29 and 291.24 cases; that is, the inter-quartile range between the 1st Qu and the 3rd Qu. To analyse the spatial structure, we next map the estimated coefficients obtained from the adaptive kernel GWR.\n\n # Ethnic\nlegend_title = expression(\"Ethnic\")\nmap_abgwr2 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr2 = map_abgwr2 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n # Long-term Illness\nlegend_title = expression(\"Long-term illness\")\nmap_abgwr3 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_lt_illness\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr3 = map_abgwr3 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\ntmap_arrange(map_abgwr2, map_abgwr3)\n\n\n\n\nAnalysing the map for long-term illness, a clear North-South divide can be identified. In the North we observed the expected positive relationship between COVID-19 and long-term illness i.e. as the share of the local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to increase. In the South, we observe the inverse pattern i.e. as the share of local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to drop. This pattern is counterintuitive but may be explained by the wider socio-economic disadvantages between the North and the South of England. The North is usually characterised by a persistent concentration of more disadvantaged neighbourhoods than the South where affluent households have tended to cluster for the last 40 years (Patias, Rowe, and Arribas-Bel 2021).\n\n\n9.6.7 Assessing statistical significance\nWhile the maps above offer valuable insights to understand the spatial pattering of relationships, they do not identify whether these associations are statistically significant. They may not be. Roughly, if a coefficient estimate has an absolute value of t greater than 1.96 and the sample is sufficiently large, then it is statistically significant. Our sample has only 150 observations, so we are more conservative and considered a coefficient to be statistically significant if it has an absolute value of t larger than 2. Note also that p-values could be computed - see Lu et al. (2014).\n\n# compute t statistic\nutla_shp$t_ethnic = ab_gwr_out$ethnic / ab_gwr_out$ethnic_se\n\n# categorise t values\nutla_shp$t_ethnic_cat <- cut(utla_shp$t_ethnic,\n breaks=c(min(utla_shp$t_ethnic), -2, 2, max(utla_shp$t_ethnic)),\n labels=c(\"sig\",\"nonsig\", \"sig\"))\n\n# map statistically significant coefs for ethnic\nlegend_title = expression(\"Ethnic: significant\")\nmap_sig = tm_shape(utla_shp) + \n tm_fill(col = \"t_ethnic_cat\", title = legend_title, legend.hist = TRUE, midpoint = NA, textNA = \"\", colorNA = \"white\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\", legend.outside = TRUE) # change background colour & place legend outside\n\nmap_sig + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n# utla count\ntable(utla_shp$t_ethnic_cat)\n\n\n sig nonsig \n 105 45 \n\n\nFor the share of nonwhite population, 67% of all local coefficients are statistically significant and these are largely in the South of England. Coefficients in the North tend to be insignificant. Through outliers exist in both regions. In the South, nonsignificant coefficients are observed in the metropolitan areas of London, Birmingham and Nottingham, while significant coefficients exist in the areas of Newcastle and Middlesbrough in the North.\n\nChallenge 3 Compute the t values for the intercept and estimated coefficient for long-term illness and create maps of their statistical significance. How many UTLAs report statistically significant coefficients?\n\n\n\n9.6.8 Collinearity in GWR\nAn important final note is: collinearity tends to be problematic in GWR models. It can be present in the data subsets to estimate local coefficients even when not observed globally Wheeler and Tiefelsdorf (2005). Collinearity can be highly problematic in the case of compositional, categorical and ordinal predictors, and may result in exact local collinearity making the search for an optimal bandwidth impossible. A recent paper suggests potential ways forward (Comber et al. 2022)." + "section": "\n9.6 Fitting a Geographically Weighted Regression", + "text": "9.6 Fitting a Geographically Weighted Regression\nGWR overcomes the limitation of the OLS regression model of generating a global set of estimates. The basic idea behind GWR is to examine the way in which the relationships between a dependent variable and a set of predictors might vary over space. GWR operates by moving a search window from one regression point to the next, working sequentially through all the existing regression points in the dataset. A set of regions is then defined around each regression point and within the search window. A regression model is then fitted to all data contained in each of the identified regions around a regression point, with data points closer to the sample point being weighted more heavily than are those farther away. This process is repeated for all samples points in the dataset. For a data set of 150 observations GWR will fit 150 weighted regression models. The resulting local estimates can then be mapped at the locations of the regression points to view possible variations in the relationships between variables.\nGraphically, GWR involves fitting a spatial kernel to the data as described in the Fig. 1. For a given regression point \\(X\\), the weight (\\(W\\)) of a data point is at a maximum at the location of the regression point. The weight decreases gradually as the distance between two points increases. A regression model is thus calibrated locally by moving the regression point across the area under study. For each location, the data are weighted differently so that the resulting estimates are unique to a particular location.\n\n\nFig. 1. GWR with fixed spatial kernel. Source: Fotheringham et al. (2002, 45).\n\n\n9.6.1 Fixed or Adaptive Kernel\nA key issue is to decide between two options of spatial kernels: a fixed kernel or an adaptive kernel. Intuitively, a fixed kernel involves using a fixed bandwidth to define a region around all regression points as displayed in Fig. 1. The extent of the kernel is determined by the distance to a given regression point, with the kernel being identical at any point in space. An adaptive kernel involves using varying bandwidth to define a region around regression points as displayed in Fig. 2. The extent of the kernel is determined by the number of nearest neighbours from a given regression point. The kernels have larger bandwidths where the data are sparse.\n\n\nFig. 2. GWR with adaptive spatial kernel. Source: Fotheringham et al. (2002, 47).\n\n\n9.6.2 Optimal Bandwidth\nA second issue is to define the extent of geographical area (i.e. optimal bandwidth) of the spatial kernel. The bandwidth is the distance beyond which a value of zero is assigned to weight observations. Larger bandwidths include a larger number of observations receiving a non-zero weight and more observations are used to fit a local regression.\nTo determine the optimal bandwidth, a cross-validation approach is applied; that is, for a location, a local regression is fitted based on a given bandwidth and used to predict the value of the dependent variable. The resulting predicted value is used to compute the residuals of the model. Residuals are compared using a series of bandwidth and the bandwidth returning the smallest local residuals are selected.\nVariance and Bias Trade off\nChoosing an optimal bandwidth involves a compromise between bias and precision. For example, a larger bandwidth will involve using a larger number of observations to fit a local regression, and hence result in reduced variance (or increased precision) but high bias of estimates. On the other hand, too small bandwidth involves using a very small number of observations resulting in increased variance but small bias. An optimal bandwidth offers a compromise between bias and variance.\n\n9.6.3 Shape of Spatial Kernel\nTwo general set of kernel functions can be distinguished: continuous kernels and kernels with compact support. Continuous kernels are used to weight all observations in the study area and includes uniform, Gaussian and Exponential kernel functions. Kernel with compact support are used to assign a nonzero weight to observations within a certain distance and a zero weight beyond it. The shape of the kernel has been reported to cause small changes to resulting estimates (Brunsdon, Fotheringham, and Charlton 1998).\n\n9.6.4 Selecting a Bandwidth\nLet’s now implement a GWR model. The first key step is to define the optimal bandwidth. We first illustrate the use of a fixed spatial kernel.\n\n9.6.4.1 Fixed Bandwidth\nCross-validation is used to search for the optimal bandwidth. Recall that this procedure compares the model residuals based on different bandwidths and chooses the optimal solution i.e. the bandwidth returning the smallest model residuals based on a given model specification. A key parameter here is the shape of the geographical weight function (gweight). We set it to be a Gaussian function which is the default. A bi-square function is recommended to reduce computational time. Since we have a simple model, a Gaussian function should not take that long. Note that we set the argument longlat to TRUE and use latitude and longitude for coordinates (coords). When longlat is set to TRUE, distances are measured in kilometres.\n\n# find optimal kernel bandwidth using cross validation\nfbw <- gwr.sel(eq1, \n data = utla_shp, \n coords=cbind( long, lat),\n longlat = TRUE,\n adapt=FALSE, \n gweight = gwr.Gauss, \n verbose = FALSE)\n\n# view selected bandwidth\nfbw\n\n[1] 29.30417\n\n\nThe result indicates that the optimal bandwidth is 39.79 kms. This means that neighbouring UTLAs within a fixed radius of 39.79 kms will be taken to estimate local regressions. To estimate a GWR, we execute the code below in which the optimal bandwidth above is used as an input in the argument bandwidth.\n\n# fit a gwr based on fixed bandwidth\nfb_gwr <- gwr(eq1, \n data = utla_shp,\n coords=cbind( long, lat),\n longlat = TRUE,\n bandwidth = fbw, \n gweight = gwr.Gauss,\n hatmatrix=TRUE, \n se.fit=TRUE)\n\nfb_gwr\n\nCall:\ngwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), \n bandwidth = fbw, gweight = gwr.Gauss, hatmatrix = TRUE, longlat = TRUE, \n se.fit = TRUE)\nKernel function: gwr.Gauss \nFixed bandwidth: 29.30417 \nSummary of GWR coefficient estimates at data points:\n Min. 1st Qu. Median 3rd Qu. Max. Global\nX.Intercept. -187.913 -42.890 93.702 211.685 792.989 63.768\nethnic -785.938 104.813 194.609 254.717 1078.854 271.096\nlt_illness -2599.119 -563.128 128.176 690.603 1507.024 216.198\nNumber of data points: 150 \nEffective number of parameters (residual: 2traceS - traceS'S): 57.11019 \nEffective degrees of freedom (residual: 2traceS - traceS'S): 92.88981 \nSigma (residual: 2traceS - traceS'S): 38.34777 \nEffective number of parameters (model: traceS): 44.65744 \nEffective degrees of freedom (model: traceS): 105.3426 \nSigma (model: traceS): 36.00992 \nSigma (ML): 30.17717 \nAICc (GWR p. 61, eq 2.33; p. 96, eq. 4.21): 1580.349 \nAIC (GWR p. 96, eq. 4.22): 1492.465 \nResidual sum of squares: 136599.2 \nQuasi-global R2: 0.7830537 \n\n\nWe will skip the interpretation of the results for now and consider them in the next section. Now, we want to focus on the overall model fit and will map the results of the \\(R^{2}\\) for the estimated local regressions. To do this, we extract the model results stored in a Spatial Data Frame (SDF) and add them to our spatial data frame utla_shp. Note that the Quasi-global \\(R^{2}\\) is very high (0.77) indicating a high in-sample prediction accuracy.\n\n# write gwr output into a data frame\nfb_gwr_out <- as.data.frame(fb_gwr$SDF)\n\nutla_shp$fmb_localR2 <- fb_gwr_out$localR2\n\n# map\n # Local R2\nlegend_title = expression(\"Fixed: Local R2\")\nmap_fbgwr1 = tm_shape(utla_shp) +\n tm_fill(col = \"fmb_localR2\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_fbgwr1 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\n\n\n\nThe map shows very high in-sample model predictions of up to 80% in relatively large UTLAs (i.e. Cornwall, Devon and Cumbria) but poor predictions in Linconshire and small UTLAs in the North West and Yorkshire & The Humber Regions and the Greater London. The spatial distribution of this pattern may reflect a potential problem that arise in the application of GWR with fixed spatial kernels. The use of fixed kernels implies that local regressions for small spatial units may be calibrated on a large number of dissimilar areas, while local regressions for large areas may be calibrated on very few data points, giving rise to estimates with large standard errors. In extreme cases, generating estimates might not be possible due to insufficient variation in small samples. In practice, this issue is relatively common if the number of geographical areas in the dataset is small.\n\n9.6.4.2 Adaptive Bandwidth\nTo reduce these problems, adaptive spatial kernels can be used. These kernels adapt in size to variations in the density of the data so that the kernels have larger bandwidths where the data are sparse and have smaller bandwidths where the data are plentiful. As above, we first need to search for the optimal bandwidth before estimating a GWR.\n\n# find optimal kernel bandwidth using cross validation\nabw <- gwr.sel(eq1, \n data = utla_shp, \n coords=cbind( long, lat),\n longlat = TRUE,\n adapt = TRUE, \n gweight = gwr.Gauss, \n verbose = FALSE)\n\n# view selected bandwidth\nabw\n\n[1] 0.03126972\n\n\nThe optimal bandwidth is 0.03 indicating the proportion of observations (or k-nearest neighbours) to be included in the weighting scheme. In this example, the optimal bandwidth indicates that for a given UTLA, 3% of its nearest neighbours should be used to calibrate the relevant local regression; that is about 5 UTLAs. The search window will thus be variable in size depending on the extent of UTLAs. Note that here the optimal bandwidth is defined based on a data point’s k-nearest neighbours. It can also be defined by geographical distance as done above for the fixed spatial kernel. We next fit a GWR based on an adaptive bandwidth.\n\n# fit a gwr based on adaptive bandwidth\nab_gwr <- gwr(eq1, \n data = utla_shp,\n coords=cbind( long, lat),\n longlat = TRUE,\n adapt = abw, \n gweight = gwr.Gauss,\n hatmatrix=TRUE, \n se.fit=TRUE)\n\nab_gwr\n\nCall:\ngwr(formula = eq1, data = utla_shp, coords = cbind(long, lat), \n gweight = gwr.Gauss, adapt = abw, hatmatrix = TRUE, longlat = TRUE, \n se.fit = TRUE)\nKernel function: gwr.Gauss \nAdaptive quantile: 0.03126972 (about 4 of 150 data points)\nSummary of GWR coefficient estimates at data points:\n Min. 1st Qu. Median 3rd Qu. Max. Global\nX.Intercept. -198.790 -28.398 113.961 226.437 346.510 63.768\nethnic -121.872 106.822 229.591 283.739 1162.123 271.096\nlt_illness -1907.098 -746.468 -125.855 798.875 1496.549 216.198\nNumber of data points: 150 \nEffective number of parameters (residual: 2traceS - traceS'S): 48.59361 \nEffective degrees of freedom (residual: 2traceS - traceS'S): 101.4064 \nSigma (residual: 2traceS - traceS'S): 36.57493 \nEffective number of parameters (model: traceS): 36.04378 \nEffective degrees of freedom (model: traceS): 113.9562 \nSigma (model: traceS): 34.50222 \nSigma (ML): 30.07257 \nAICc (GWR p. 61, eq 2.33; p. 96, eq. 4.21): 1546.029 \nAIC (GWR p. 96, eq. 4.22): 1482.809 \nResidual sum of squares: 135653.9 \nQuasi-global R2: 0.7845551 \n\n\n\n9.6.5 Model fit\nAssessing the global fit of the model, marginal improvements are observed. The \\(AIC\\) and Residual sum of squares experienced marginal reductions, while the \\(R^{2}\\) increased compared to the GRW based on a fixed kernel. To gain a better understanding of these changes, as above, we map the \\(R^{2}\\) values for the estimated local regressions.\n\n# write gwr output into a data frame\nab_gwr_out <- as.data.frame(ab_gwr$SDF)\n\nutla_shp$amb_ethnic <- ab_gwr_out$ethnic\nutla_shp$amb_lt_illness <- ab_gwr_out$lt_illness\nutla_shp$amb_localR2 <- ab_gwr_out$localR2\n\n# map\n # Local R2\nlegend_title = expression(\"Adaptive: Local R2\")\nmap_abgwr1 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_localR2\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr1 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\n\n\n\nThe map reveals notable improvements in local estimates for UTLAs within West and East Midlands, the South East, South West and East of England. Estimates are still poor in hot spot UTLAs concentrating confirmed cases of COVID-19, such as the Greater London, Liverpool and Newcastle areas.\n\n9.6.6 Interpretation\nThe key strength of GWR models is in identifying patterns of spatial variation in the associations between pairs of variables. The results reveal how these coefficients vary across the 150 UTLAs of England. To examine this variability, let’s first focus on the adaptive GWR output reported in Section 8.6.4.2. The output includes a summary of GWR coefficient estimates at various data points. The last column reports the global estimates which are the same as the coefficients from the OLS regression we fitted at the start of our analysis. For our variable nonwhite ethnic population, the GWR outputs reveals that local coefficients range from a minimum value of -148.41 to a maximum value of 1076.84, indicating that one percentage point increase in the share of nonwhite ethnic population is associated with a a reduction of 148.41 in the number of cumulative confirmed cases of COVID-19 per 100,000 people in some UTLAs and an increase of 1076.84 in others. For half of the UTLAs in the dataset, as the share of nonwhite ethnic population increases by one percentage point, the rate of COVID-19 will increase between 106.29 and 291.24 cases; that is, the inter-quartile range between the 1st Qu and the 3rd Qu. To analyse the spatial structure, we next map the estimated coefficients obtained from the adaptive kernel GWR.\n\n # Ethnic\nlegend_title = expression(\"Ethnic\")\nmap_abgwr2 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_ethnic\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr2 = map_abgwr2 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n # Long-term Illness\nlegend_title = expression(\"Long-term illness\")\nmap_abgwr3 = tm_shape(utla_shp) +\n tm_fill(col = \"amb_lt_illness\", title = legend_title, palette = magma(256), style = \"cont\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\") # change background colour\nmap_abgwr3 = map_abgwr3 + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\ntmap_arrange(map_abgwr2, map_abgwr3)\n\n\n\n\n\n\n\nAnalysing the map for long-term illness, a clear North-South divide can be identified. In the North we observed the expected positive relationship between COVID-19 and long-term illness i.e. as the share of the local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to increase. In the South, we observe the inverse pattern i.e. as the share of local population suffering from long-term illness rises, the cumulative number of positive COVID-19 cases is expected to drop. This pattern is counterintuitive but may be explained by the wider socio-economic disadvantages between the North and the South of England. The North is usually characterised by a persistent concentration of more disadvantaged neighbourhoods than the South where affluent households have tended to cluster for the last 40 years (Patias, Rowe, and Arribas-Bel 2021).\n\n9.6.7 Assessing statistical significance\nWhile the maps above offer valuable insights to understand the spatial pattering of relationships, they do not identify whether these associations are statistically significant. They may not be. Roughly, if a coefficient estimate has an absolute value of t greater than 1.96 and the sample is sufficiently large, then it is statistically significant. Our sample has only 150 observations, so we are more conservative and considered a coefficient to be statistically significant if it has an absolute value of t larger than 2. Note also that p-values could be computed - see Lu et al. (2014).\n\n# compute t statistic\nutla_shp$t_ethnic = ab_gwr_out$ethnic / ab_gwr_out$ethnic_se\n\n# categorise t values\nutla_shp$t_ethnic_cat <- cut(utla_shp$t_ethnic,\n breaks=c(min(utla_shp$t_ethnic), -2, 2, max(utla_shp$t_ethnic)),\n labels=c(\"sig\",\"nonsig\", \"sig\"))\n\n# map statistically significant coefs for ethnic\nlegend_title = expression(\"Ethnic: significant\")\nmap_sig = tm_shape(utla_shp) + \n tm_fill(col = \"t_ethnic_cat\", title = legend_title, legend.hist = TRUE, midpoint = NA, textNA = \"\", colorNA = \"white\") + # add fill\n tm_borders(col = \"white\", lwd = .1) + # add borders\n tm_compass(type = \"arrow\", position = c(\"right\", \"top\") , size = 5) + # add compass\n tm_scale_bar(breaks = c(0,1,2), text.size = 0.7, position = c(\"center\", \"bottom\")) + # add scale bar\n tm_layout(bg.color = \"white\", legend.outside = TRUE) # change background colour & place legend outside\n\nmap_sig + tm_shape(reg_shp) + # add region boundaries\n tm_borders(col = \"white\", lwd = .5) # add borders\n\n\n\n\n\n\n# utla count\ntable(utla_shp$t_ethnic_cat)\n\n\n sig nonsig \n 105 45 \n\n\nFor the share of nonwhite population, 67% of all local coefficients are statistically significant and these are largely in the South of England. Coefficients in the North tend to be insignificant. Through outliers exist in both regions. In the South, nonsignificant coefficients are observed in the metropolitan areas of London, Birmingham and Nottingham, while significant coefficients exist in the areas of Newcastle and Middlesbrough in the North.\n\nChallenge 3 Compute the t values for the intercept and estimated coefficient for long-term illness and create maps of their statistical significance. How many UTLAs report statistically significant coefficients?\n\n\n9.6.8 Collinearity in GWR\nAn important final note is: collinearity tends to be problematic in GWR models. It can be present in the data subsets to estimate local coefficients even when not observed globally Wheeler and Tiefelsdorf (2005). Collinearity can be highly problematic in the case of compositional, categorical and ordinal predictors, and may result in exact local collinearity making the search for an optimal bandwidth impossible. A recent paper suggests potential ways forward (Comber et al. 2022).", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { "objectID": "09-gwr.html#questions", "href": "09-gwr.html#questions", "title": "9  Geographically Weighted Regression", - "section": "9.7 Questions", - "text": "9.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nFit a GWR model using a fixed and an adaptive bandwidth.\nCreate a multiple map figure to analyse the spatial variation of coefficients.\n\nAnalyse and discuss:\n\nHow regression coefficients vary across space. Do they vary in size and statistical significance?\nWhat is the appropriate bandwidth for your GWR? Why?\n\n\n\n\n\nBelsley, David A, Edwin Kuh, and Roy E Welsch. 2005. Regression Diagnostics: Identifying Influential Data and Sources of Collinearity. Vol. 571. John Wiley & Sons.\n\n\nBrunsdon, Chris, Stewart Fotheringham, and Martin Charlton. 1998. “Geographically Weighted Regression.” Journal of the Royal Statistical Society: Series D (The Statistician) 47 (3): 431–43.\n\n\nComber, Alexis, Christopher Brunsdon, Martin Charlton, Guanpeng Dong, Richard Harris, Binbin Lu, Yihe Lü, et al. 2022. “A Route Map for Successful Applications of Geographically Weighted Regression.” Geographical Analysis 55 (1): 155–78. https://doi.org/10.1111/gean.12316.\n\n\nFotheringham, Stewart, Chris Brunsdon, and Martin Charlton. 2002. Geographically Weighted Regression. John Wiley & Sons.\n\n\nLu, Binbin, Paul Harris, Martin Charlton, and Chris Brunsdon. 2014. “The GWmodel r Package: Further Topics for Exploring Spatial Heterogeneity Using Geographically Weighted Models.” Geo-Spatial Information Science 17 (2): 85–101.\n\n\nPatias, Nikos, Francisco Rowe, and Dani Arribas-Bel. 2021. “Trajectories of Neighbourhood Inequality in Britain: Unpacking Inter-Regional Socioeconomic Imbalances, 1971-2011.” The Geographical Journal 188 (2): 150–65. https://doi.org/10.1111/geoj.12420.\n\n\nWheeler, David, and Michael Tiefelsdorf. 2005. “Multicollinearity and Correlation Among Local Regression Coefficients in Geographically Weighted Regression.” Journal of Geographical Systems 7 (2): 161–87." + "section": "\n9.7 Questions", + "text": "9.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nFit a GWR model using a fixed and an adaptive bandwidth.\nCreate a multiple map figure to analyse the spatial variation of coefficients.\n\nAnalyse and discuss:\n\nHow regression coefficients vary across space. Do they vary in size and statistical significance?\nWhat is the appropriate bandwidth for your GWR? Why?\n\n\n\n\n\n\n\nBelsley, David A, Edwin Kuh, and Roy E Welsch. 2005. Regression Diagnostics: Identifying Influential Data and Sources of Collinearity. Vol. 571. John Wiley & Sons.\n\n\nBrunsdon, Chris, Stewart Fotheringham, and Martin Charlton. 1998. “Geographically Weighted Regression.” Journal of the Royal Statistical Society: Series D (The Statistician) 47 (3): 431–43.\n\n\nComber, Alexis, Christopher Brunsdon, Martin Charlton, Guanpeng Dong, Richard Harris, Binbin Lu, Yihe Lü, et al. 2022. “A Route Map for Successful Applications of Geographically Weighted Regression.” Geographical Analysis 55 (1): 155–78. https://doi.org/10.1111/gean.12316.\n\n\nFotheringham, Stewart, Chris Brunsdon, and Martin Charlton. 2002. Geographically Weighted Regression. John Wiley & Sons.\n\n\nLu, Binbin, Paul Harris, Martin Charlton, and Chris Brunsdon. 2014. “The GWmodel r Package: Further Topics for Exploring Spatial Heterogeneity Using Geographically Weighted Models.” Geo-Spatial Information Science 17 (2): 85–101.\n\n\nPatias, Nikos, Francisco Rowe, and Dani Arribas-Bel. 2021. “Trajectories of Neighbourhood Inequality in Britain: Unpacking Inter-Regional Socioeconomic Imbalances, 1971-2011.” The Geographical Journal 188 (2): 150–65. https://doi.org/10.1111/geoj.12420.\n\n\nWheeler, David, and Michael Tiefelsdorf. 2005. “Multicollinearity and Correlation Among Local Regression Coefficients in Geographically Weighted Regression.” Journal of Geographical Systems 7 (2): 161–87.", + "crumbs": [ + "9  Geographically Weighted Regression" + ] }, { - "objectID": "10-st_analysis.html#dependencies", - "href": "10-st_analysis.html#dependencies", + "objectID": "10-st_analysis.html", + "href": "10-st_analysis.html", "title": "10  Spatio-Temporal Analysis", - "section": "10.1 Dependencies", - "text": "10.1 Dependencies\nThis chapter uses the following libraries:\n\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Nice colour schemes\nlibrary(viridis) \n# Obtain correlation coefficients\nlibrary(corrplot)\n# Highlight data on plots\nlibrary(gghighlight)\n# Analysing spatio-temporal data\n#library(STRbook)\nlibrary(spacetime)\n# Date parsing and manipulation\nlibrary(lubridate)\n# Applied statistics\nlibrary(MASS)\n# Statistical tests for linear regression models\nlibrary(lmtest)\n# Fit spatial random effects models\nlibrary(FRK)\n# Exportable regression tables\nlibrary(jtools)" + "section": "", + "text": "10.1 Dependencies\nThis chapter uses the following libraries:\n# Data manipulation, transformation and visualisation\nlibrary(tidyverse)\n# Nice tables\nlibrary(kableExtra)\n# Simple features (a standardised way to encode vector data ie. points, lines, polygons)\nlibrary(sf) \n# Spatial objects conversion\nlibrary(sp) \n# Thematic maps\nlibrary(tmap) \n# Nice colour schemes\nlibrary(viridis) \n# Obtain correlation coefficients\nlibrary(corrplot)\n# Highlight data on plots\nlibrary(gghighlight)\n# Analysing spatio-temporal data\n#library(STRbook)\nlibrary(spacetime)\n# Date parsing and manipulation\nlibrary(lubridate)\n# Applied statistics\nlibrary(MASS)\n# Statistical tests for linear regression models\nlibrary(lmtest)\n# Fit spatial random effects models\nlibrary(FRK)\n# Exportable regression tables\nlibrary(jtools)", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#data", "href": "10-st_analysis.html#data", "title": "10  Spatio-Temporal Analysis", - "section": "10.2 Data", - "text": "10.2 Data\nFor this chapter, we will use data on:\n\nCOVID-19 confirmed cases from 30th January, 2020 to 21st April, 2020 from Public Health England via the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government. The data are at the ONS Upper Tier Local Authority (UTLA) level - also known as Counties and Unitary Authorities.\n\nFor a full list of the variables included in the data sets used in this chapter, see the readme file in the sta data folder.1. Before we get our hands on the data, there are some important concepts that need to be introduced. They provide a useful framework to understand the complex structure of spatio-temporal data. Let’s start by first highlighling the importance of spatio-temporal analysis." + "section": "\n10.2 Data", + "text": "10.2 Data\nFor this chapter, we will use data on:\n\nCOVID-19 confirmed cases from 30th January, 2020 to 21st April, 2020 from Public Health England via the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government. The data are at the ONS Upper Tier Local Authority (UTLA) level - also known as Counties and Unitary Authorities.\n\nFor a full list of the variables included in the data sets used in this chapter, see the readme file in the sta data folder.1. Before we get our hands on the data, there are some important concepts that need to be introduced. They provide a useful framework to understand the complex structure of spatio-temporal data. Let’s start by first highlighling the importance of spatio-temporal analysis.\n1 Read the file in R by executing read_tsv(\"data/sta/readme.txt\"). Ensure the library readr is installed before running read_tsv.", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#why-spatio-temporal-analysis", "href": "10-st_analysis.html#why-spatio-temporal-analysis", "title": "10  Spatio-Temporal Analysis", - "section": "10.3 Why Spatio-Temporal Analysis?", - "text": "10.3 Why Spatio-Temporal Analysis?\nInvestigating the spatial patterns of human processes as we have done so far in this book only offers a partial incomplete representation of these processes. It does not allow understanding of the temporal evolution of these processes. Human processes evolve in space and time. Human mobility is a inherent geographical process which changes over the course of the day, with peaks at rush hours and high concentration towards employment, education and retail centres. Exposure to air pollution changes with local climatic conditions, and emission and concentration of atmospheric pollutants which fluctuate over time. The rate of disease spread varies over space and may significantly change over time as we have seen during the current outbreak, with flattened or rapid declining trends in Australia, New Zealand and South Korea but fast proliferation in the United Kingdom and the United States. Only by considering time and space together we can address how geographic entities change over time and why they change. A large part of how and why of such change occurs is due to interactions across space and time, and multiple processes. It is essential to understand the past to inform our understanding of the present and make predictions about the future.\n\n10.3.1 Spatio-temporal Data Structures\nA first key element is to understand the structure of spatio-temporal data. Spatio-temporal data incorporate two dimensions. At one end, we have the temporal dimension. In quantitative analysis, time-series data are used to capture geographical processes at regular or irregular intervals; that is, in a continuous (daily) or discrete (only when a event occurs) temporal scale. At another end, we have the spatial dimension. We often use spatial data as temporal aggregations or temporally frozen states (or ‘snapshots’) of a geographical process - this is what we have done so far. Recall that spatial data can be capture in different geographical units, such as areal or lattice, points, flows or trajectories - refer to the introductory lecture in Week 1. Relatively few ways exist to formally integrate temporal and spatial data in consistent analytical framework. Two notable exceptions in R are the packages TraMiner (Gabadinho et al. 2009) and spacetime (Pebesma et al. 2012). We use the class definitions defined in the R package spacetime. These classes extend those used for spatial data in sp and time-series data in xts. Next a brief introduction to concepts that facilitate thinking about spatio-temporal data structures.\n\n10.3.1.1 Type of Table\nSpatio-temporal data can be conceptualised as three main different types of tables:\n\ntime-wide: a table in which columns correspond to different time points\nspace-wide: a table in which columns correspond to different spatial location\nlong formats: a table in which each row-column pair corresponds to a specific time and spatial location (or space coordinate)\n\n\nNote that data in long format are space inefficient because spatial coordinates and time attributes are required for each data point. Yet, data in this format are relatively easy to manipulate via packages such as dplyr and tidyr, and visualise using ggplot2. These packages are designed to work with data in long format.\n\n\n\n10.3.1.2 Type of Spatio-Temporal Object\nTo integrate spatio-temporal data, spatio-temporal objects are needed. We consider four different spatio-temporal frames (STFs) or objects which can be defined via the package spacetime:\n\nFull grid (STF): an object containing data on all possible locations in all time points in a sequence of data;\nSparse grid (STS): an object similar to STF but only containing non-missing space-time data combinations;\nIrregular (STI): an object with an irregular space-time data structure, where each point is allocated a spatial coordinate and a time stamp;\nSimple Trajectories (STT): an object containig a sequence of space-time points that form trajectories.\n\nMore details on these spatio-temporal structures, construction and manipulation, see Pebesma et al. (2012). Enough theory, let’s code!" + "section": "\n10.3 Why Spatio-Temporal Analysis?", + "text": "10.3 Why Spatio-Temporal Analysis?\nInvestigating the spatial patterns of human processes as we have done so far in this book only offers a partial incomplete representation of these processes. It does not allow understanding of the temporal evolution of these processes. Human processes evolve in space and time. Human mobility is a inherent geographical process which changes over the course of the day, with peaks at rush hours and high concentration towards employment, education and retail centres. Exposure to air pollution changes with local climatic conditions, and emission and concentration of atmospheric pollutants which fluctuate over time. The rate of disease spread varies over space and may significantly change over time as we have seen during the current outbreak, with flattened or rapid declining trends in Australia, New Zealand and South Korea but fast proliferation in the United Kingdom and the United States. Only by considering time and space together we can address how geographic entities change over time and why they change. A large part of how and why of such change occurs is due to interactions across space and time, and multiple processes. It is essential to understand the past to inform our understanding of the present and make predictions about the future.\n\n10.3.1 Spatio-temporal Data Structures\nA first key element is to understand the structure of spatio-temporal data. Spatio-temporal data incorporate two dimensions. At one end, we have the temporal dimension. In quantitative analysis, time-series data are used to capture geographical processes at regular or irregular intervals; that is, in a continuous (daily) or discrete (only when a event occurs) temporal scale. At another end, we have the spatial dimension. We often use spatial data as temporal aggregations or temporally frozen states (or ‘snapshots’) of a geographical process - this is what we have done so far. Recall that spatial data can be capture in different geographical units, such as areal or lattice, points, flows or trajectories - refer to the introductory lecture in Week 1. Relatively few ways exist to formally integrate temporal and spatial data in consistent analytical framework. Two notable exceptions in R are the packages TraMiner (Gabadinho et al. 2009) and spacetime (Pebesma et al. 2012). We use the class definitions defined in the R package spacetime. These classes extend those used for spatial data in sp and time-series data in xts. Next a brief introduction to concepts that facilitate thinking about spatio-temporal data structures.\n\n10.3.1.1 Type of Table\nSpatio-temporal data can be conceptualised as three main different types of tables:\n\ntime-wide: a table in which columns correspond to different time points\nspace-wide: a table in which columns correspond to different spatial location\nlong formats: a table in which each row-column pair corresponds to a specific time and spatial location (or space coordinate)\n\n\nNote that data in long format are space inefficient because spatial coordinates and time attributes are required for each data point. Yet, data in this format are relatively easy to manipulate via packages such as dplyr and tidyr, and visualise using ggplot2. These packages are designed to work with data in long format.\n\n\n10.3.1.2 Type of Spatio-Temporal Object\nTo integrate spatio-temporal data, spatio-temporal objects are needed. We consider four different spatio-temporal frames (STFs) or objects which can be defined via the package spacetime:\n\nFull grid (STF): an object containing data on all possible locations in all time points in a sequence of data;\nSparse grid (STS): an object similar to STF but only containing non-missing space-time data combinations;\nIrregular (STI): an object with an irregular space-time data structure, where each point is allocated a spatial coordinate and a time stamp;\nSimple Trajectories (STT): an object containig a sequence of space-time points that form trajectories.\n\nMore details on these spatio-temporal structures, construction and manipulation, see Pebesma et al. (2012). Enough theory, let’s code!", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#data-wrangling", "href": "10-st_analysis.html#data-wrangling", "title": "10  Spatio-Temporal Analysis", - "section": "10.4 Data Wrangling", - "text": "10.4 Data Wrangling\nThis section illustrates the complexities of handling spatio-temporal data. It discusses good practices in data manipulation and construction of a Space Time Irregular Data Frame (STIDF) object. Three key requirements to define a STFDF object are:\n\nHave a data frame in long format i.e. a location-time pair data frame\nDefine a time stamp\nConstruct the spatio-temporal object of class STIDF by indicating the spatial and temporal coordinates\n\nLet’s now read all the required data. While we can have all data in a single data frame, you will find helpful to have separate data objects to identify:\n\nspatial locations\ntemporal units\ndata\n\nThese data objects correspond to locs, time, and covid19 and censusimd below. Throughout the chapter you will notice that we switch between the various data frames when convinient, depending on the operation.\n\n# clear workspace\nrm(list=ls())\n\n# read ONS UTLA shapefile\nutla_shp <- st_read(\"data/sta/ons_utla.shp\") \n\nReading layer `ons_utla' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/sta/ons_utla.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 150 features and 11 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: Transverse_Mercator\n\n# create table of locations\nlocs <- utla_shp %>% as.data.frame() %>%\n dplyr::select(objct, cty19c, ctyu19nm, long, lat, st_rs) \n\n# read time data frame\ntime <- read_csv(\"data/sta/reporting_dates.csv\")\n\n# read COVID-19 data in long format\ncovid19 <- read_csv(\"data/sta/covid19_cases.csv\")\n\n# read census and IMD data\ncensusimd <- read_csv(\"data/sta/2011census_2019imd_utla.csv\")\n\nIf we explore the structure of the data via head and str, we can see we have data on daily and cumulative new COVID-19 cases for 150 spatial units (i.e. UTLAs) over 71 time points from January 30th to April 21st. We also have census and IMD data for a range of attributes.\n\nhead(covid19, 3)\n\n# A tibble: 3 × 6\n Area.name Area.code Area.type date Daily…¹ Cumul…²\n \n1 Barking and Dagenham E09000002 Upper tier local au… 2020-01-30 0 0\n2 Barnet E09000003 Upper tier local au… 2020-01-30 0 0\n3 Barnsley E08000016 Upper tier local au… 2020-01-30 0 0\n# … with abbreviated variable names ¹​Daily.lab.confirmed.cases,\n# ²​Cumulative.lab.confirmed.cases\n\n\nOnce we have understood the structure of the data, we first need to confirm if the covid19 data are in wide or long format. Luckily they are in long format; otherwise, we would have needed to transform the data from wide to long format. Useful functions to achieve this include pivot_longer (pivot_longer) which has superseded gather (spread) in the tidyr package. Note that the covid19 data frame has 10,650 observations (i.e. rows); that is, 150 UTLAs * 71 daily observations.\nWe then define a regular time stamp for our temporal data. We use the lubridate package to do this. A key advantage of lubridate is that it automatically recognises the common separators used when recording dates (“-”, “/”, “.”, and ““). As a result, you only need to focus on specifying the order of the date elements to determine the parsing function applied. Below we check the structure of our time data, define a time stamp and create separate variables for days, weeks, months and year.\n\nNote that working with dates can be a complex task. A good discussion of these complexities is provided here.\n\n\n# check the time structure used for reporting covid cases\nhead(covid19$date, 5)\n\n[1] \"2020-01-30\" \"2020-01-30\" \"2020-01-30\" \"2020-01-30\" \"2020-01-30\"\n\n# parsing data into a time stamp\ncovid19$date <- ymd(covid19$date)\nclass(covid19$date)\n\n[1] \"Date\"\n\n# separate date variable into day,week, month and year variables\ncovid19$day <- day(covid19$date)\ncovid19$week <- week(covid19$date) # week of the year\ncovid19$month <- month(covid19$date)\ncovid19$year <- year(covid19$date)\n\nOnce defined the time stamp, we need to add the spatial information contained in our shapefile to create a spatio-temporal data frame.\n\n# join dfs\ncovid19_spt <- left_join(utla_shp, covid19, by = c(\"ctyu19nm\" = \"Area.name\"))\n\nWe now have all the components to build a spatio-temporal object of class STIDF using STIDF from the spacetime package:\n\n# identifying spatial fields\nspat_part <- as(dplyr::select(covid19_spt, -c(bng_e, bng_n, Area.code, Area.type, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, date, day, week, month, year)), Class = \"Spatial\")\n\n# identifying temporal fields\ntemp_part <- covid19_spt$date\n\n# identifying data\ncovid19_data <- covid19_spt %>% dplyr::select(c(Area.code, Area.type, date, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, day, week, month, year)) %>%\n as.data.frame()\n\n# construct STIDF object\ncovid19_stobj <- STIDF(sp = spat_part, # spatial fields\n time = temp_part, # time fields\n data = covid19_data) # data\n \nclass(covid19_stobj)\n\n[1] \"STIDF\"\nattr(,\"package\")\n[1] \"spacetime\"\n\n\nWe now add census and IMD variables. For the purposes of this Chapter, we only add total population and long-term sick or disabled population counts. You can add more variables by adding their names in the select function.\n\n# select pop data\npop <- censusimd %>% dplyr::select(\"UTLA19NM\", \"Residents\", \"Longterm_sick_or_disabled\")\n# join dfs\ncovid19_spt <- left_join(covid19_spt, pop,\n by = c(\"ctyu19nm\" = \"UTLA19NM\"))\ncovid19 <- left_join(covid19, pop, by = c(\"Area.name\" = \"UTLA19NM\"))" + "section": "\n10.4 Data Wrangling", + "text": "10.4 Data Wrangling\nThis section illustrates the complexities of handling spatio-temporal data. It discusses good practices in data manipulation and construction of a Space Time Irregular Data Frame (STIDF) object. Three key requirements to define a STFDF object are:\n\nHave a data frame in long format i.e. a location-time pair data frame\nDefine a time stamp\nConstruct the spatio-temporal object of class STIDF by indicating the spatial and temporal coordinates\n\nLet’s now read all the required data. While we can have all data in a single data frame, you will find helpful to have separate data objects to identify:\n\nspatial locations\ntemporal units\ndata\n\nThese data objects correspond to locs, time, and covid19 and censusimd below. Throughout the chapter you will notice that we switch between the various data frames when convinient, depending on the operation.\n\n# clear workspace\nrm(list=ls())\n\n# read ONS UTLA shapefile\nutla_shp <- st_read(\"data/sta/ons_utla.shp\") \n\nReading layer `ons_utla' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/sta/ons_utla.shp' \n using driver `ESRI Shapefile'\nSimple feature collection with 150 features and 11 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: Transverse_Mercator\n\n# create table of locations\nlocs <- utla_shp %>% as.data.frame() %>%\n dplyr::select(objct, cty19c, ctyu19nm, long, lat, st_rs) \n\n# read time data frame\ntime <- read_csv(\"data/sta/reporting_dates.csv\")\n\n# read COVID-19 data in long format\ncovid19 <- read_csv(\"data/sta/covid19_cases.csv\")\n\n# read census and IMD data\ncensusimd <- read_csv(\"data/sta/2011census_2019imd_utla.csv\")\n\nIf we explore the structure of the data via head and str, we can see we have data on daily and cumulative new COVID-19 cases for 150 spatial units (i.e. UTLAs) over 71 time points from January 30th to April 21st. We also have census and IMD data for a range of attributes.\n\nhead(covid19, 3)\n\n# A tibble: 3 × 6\n Area.name Area.code Area.type date Daily.lab.confirmed.…¹\n <chr> <chr> <chr> <date> <dbl>\n1 Barking and Dagenham E09000002 Upper tier l… 2020-01-30 0\n2 Barnet E09000003 Upper tier l… 2020-01-30 0\n3 Barnsley E08000016 Upper tier l… 2020-01-30 0\n# ℹ abbreviated name: ¹​Daily.lab.confirmed.cases\n# ℹ 1 more variable: Cumulative.lab.confirmed.cases <dbl>\n\n\nOnce we have understood the structure of the data, we first need to confirm if the covid19 data are in wide or long format. Luckily they are in long format; otherwise, we would have needed to transform the data from wide to long format. Useful functions to achieve this include pivot_longer (pivot_longer) which has superseded gather (spread) in the tidyr package. Note that the covid19 data frame has 10,650 observations (i.e. rows); that is, 150 UTLAs * 71 daily observations.\nWe then define a regular time stamp for our temporal data. We use the lubridate package to do this. A key advantage of lubridate is that it automatically recognises the common separators used when recording dates (“-”, “/”, “.”, and ““). As a result, you only need to focus on specifying the order of the date elements to determine the parsing function applied. Below we check the structure of our time data, define a time stamp and create separate variables for days, weeks, months and year.\n\nNote that working with dates can be a complex task. A good discussion of these complexities is provided here.\n\n\n# check the time structure used for reporting covid cases\nhead(covid19$date, 5)\n\n[1] \"2020-01-30\" \"2020-01-30\" \"2020-01-30\" \"2020-01-30\" \"2020-01-30\"\n\n# parsing data into a time stamp\ncovid19$date <- ymd(covid19$date)\nclass(covid19$date)\n\n[1] \"Date\"\n\n# separate date variable into day,week, month and year variables\ncovid19$day <- day(covid19$date)\ncovid19$week <- week(covid19$date) # week of the year\ncovid19$month <- month(covid19$date)\ncovid19$year <- year(covid19$date)\n\nOnce defined the time stamp, we need to add the spatial information contained in our shapefile to create a spatio-temporal data frame.\n\n# join dfs\ncovid19_spt <- left_join(utla_shp, covid19, by = c(\"ctyu19nm\" = \"Area.name\"))\n\nWe now have all the components to build a spatio-temporal object of class STIDF using STIDF from the spacetime package:\n\n# identifying spatial fields\nspat_part <- as(dplyr::select(covid19_spt, -c(bng_e, bng_n, Area.code, Area.type, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, date, day, week, month, year)), Class = \"Spatial\")\n\n# identifying temporal fields\ntemp_part <- covid19_spt$date\n\n# identifying data\ncovid19_data <- covid19_spt %>% dplyr::select(c(Area.code, Area.type, date, Daily.lab.confirmed.cases, Cumulative.lab.confirmed.cases, day, week, month, year)) %>%\n as.data.frame()\n\n# construct STIDF object\ncovid19_stobj <- STIDF(sp = spat_part, # spatial fields\n time = temp_part, # time fields\n data = covid19_data) # data\n \nclass(covid19_stobj)\n\n[1] \"STIDF\"\nattr(,\"package\")\n[1] \"spacetime\"\n\n\nWe now add census and IMD variables. For the purposes of this Chapter, we only add total population and long-term sick or disabled population counts. You can add more variables by adding their names in the select function.\n\n# select pop data\npop <- censusimd %>% dplyr::select(\"UTLA19NM\", \"Residents\", \"Longterm_sick_or_disabled\")\n# join dfs\ncovid19_spt <- left_join(covid19_spt, pop,\n by = c(\"ctyu19nm\" = \"UTLA19NM\"))\ncovid19 <- left_join(covid19, pop, by = c(\"Area.name\" = \"UTLA19NM\"))", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#exploring-spatio-temporal-data", "href": "10-st_analysis.html#exploring-spatio-temporal-data", "title": "10  Spatio-Temporal Analysis", - "section": "10.5 Exploring Spatio-Temporal Data", - "text": "10.5 Exploring Spatio-Temporal Data\nWe now have all the required data in place. In this section various methods of data visualisation are illustrated before key dimensions of the data are explored. Both of these types of exploration can be challenging as one or more dimensions in space and one in time need to be interrogated.\n\n10.5.1 Visualisation\nIn the context spatio-temporal data, a first challenge is data visualization. Visualising more than two dimensions of spatio-temporal data, so it is helpful to slice or aggregate the data over a dimension, use color, or build animations through time. Before exploring the data, we need to define our key variable of interest; that is, the number of confirmed COVID-19 cases per 100,000 people. We also compute the cumulative number of confirmed COVID-19 cases per 100,000 people as it may be handy in some analyses.\nFisrt create variable to be analysed:\n\n# rate of new covid-19 infection\ncovid19_spt$n_covid19_r <- round( (covid19_spt$Daily.lab.confirmed.cases / covid19_spt$Residents) * 100000)\ncovid19$n_covid19_r <- round( (covid19$Daily.lab.confirmed.cases / covid19$Residents) * 100000 )\n\n# risk of cumulative covid-19 infection\ncovid19_spt$c_covid19_r <- round( (covid19_spt$Cumulative.lab.confirmed.cases / covid19_spt$Residents) * 100000)\ncovid19$c_covid19_r <- round( (covid19$Cumulative.lab.confirmed.cases / covid19$Residents) * 100000)\n\n\n10.5.1.1 Spatial Plots\nOne way to visualise the data is using spatial plots; that is, snapshots of a geographic process for a given time period. Data can be mapped in different ways using clorepleth, countour or surface plots. The key aim of these maps is to understand how the overall extent of spatial variation and local patterns of spatial concentration change over time. Below we visualise the weekly number of confirmed COVID-19 cases per 100,000 people.\n\nNote that Weeks range from 5 to 16 as they refer to calendar weeks. Calendar week 5 is when the first COVID-19 case in England was reported.\n\n\n# create data frame for new cases by week\ndaycases_week <- covid19_spt %>% \n group_by(week, ctyu19nm, \n as.character(cty19c), \n Residents) %>%\n summarise(n_daycases = sum(Daily.lab.confirmed.cases)) \n\n# weekly rate of new covid-19 infection\ndaycases_week$wn_covid19_r <- (daycases_week$n_daycases / daycases_week$Residents) * 100000\n\n# map\nlegend_title = expression(\"Cumulative Cases per 100,000 Population\")\ntm_shape(daycases_week) +\n tm_fill(\"wn_covid19_r\", title = legend_title, palette = magma(256), style =\"cont\", legend.hist=FALSE, legend.is.portrait=FALSE) +\n tm_facets(by = \"week\", ncol = 4) +\n tm_borders(col = \"white\", lwd = .1) + # add borders +\n tm_layout(bg.color = \"white\", # change background colour\n legend.outside = TRUE, # legend outside\n legend.outside.position = \"bottom\",\n legend.stack = \"horizontal\",\n legend.title.size = 2,\n legend.width = 1,\n legend.height = 1,\n panel.label.size = 3,\n main.title = \"New COVID-19 Cases by Calendar Week, UTLA, England\") \n\nWarning in pre_process_gt(x, interactive = interactive, orig_crs =\ngm$shape.orig_crs): legend.width controls the width of the legend within a map.\nPlease use legend.outside.size to control the width of the outside legend\n\n\n\n\n\nThe series of maps reveal a stable pattern of low reported cases from calendar weeks 5 to 11. From week 12 a number of hot spots emerged, notably in London, Birmingham, Cumbria and subsequently around Liverpool. The intensity of new cases seem to have started to decline from week 15; yet, it is important to note that week 16 display reported cases for only two days.\n\n\n10.5.1.2 Time-Series Plots\nTime-series plots can be used to capture a different dimension of the process in analysis. They can be used to better understand changes in an observation location, an aggregation of observations, or multiple locations simultaneously over time. We plot the cumulative number of COVID-19 cases per 100,000 people for UTLAs reporting over 310 cases. The plots identify the UTLAs in London, Newcastle and Sheffield reporting the largest numbers of COVID-19 cases. The plots also reveal that there has been a steady increase in the number of cases, with some differences. While cases have steadily increase in Brent and Southwark since mid March, the rise has been more sudden in Sunderland. The plots also reveal a possible case of misreporting in Sutton towards the end of the series.\n\ntsp <- ggplot(data = covid19_spt,\n mapping = aes(x = date, y = c_covid19_r,\n group = ctyu19nm))\ntsp + geom_line(color = \"blue\") + \n gghighlight(max(c_covid19_r) > 310, use_direct_label = FALSE) +\n labs(title= paste(\" \"), x=\"Date\", y=\"Cumulative Cases per 100,000\") +\n theme_classic() +\n theme(plot.title=element_text(size = 20)) +\n theme(axis.text=element_text(size=16)) +\n theme(axis.title.y = element_text(size = 18)) +\n theme(axis.title.x = element_text(size = 18)) +\n theme(plot.subtitle=element_text(size = 16)) +\n theme(axis.title=element_text(size=20, face=\"plain\")) +\n facet_wrap(~ ctyu19nm)\n\n\n\n\n\n\n10.5.1.3 Hovmöller Plots\nAn alternative visualisation is a Hovmöller plot - sometimes known as heatmap. It is a two-dimensional space-time representation in which space is collapsed onto one dimension against time. Hovmöller plots can easily be generated if the data are arranged on a space-time grid; however, this is rarely the case. Luckily we have ggplot! which can do magic rearranging the data as needed. Below we produce a Hovmöller plot for UTLAs with resident populations over 260,000. The plot makes clear that the critical period of COVID-19 spread has been during April despite the implementation of a series of social distancing measures by the government.\n\nggplot(data = dplyr::filter(covid19_spt, Residents > 260000), \n mapping = aes(x= date, y= reorder(ctyu19nm, c_covid19_r), fill= c_covid19_r)) +\n geom_tile() +\n scale_fill_viridis(name=\"New Cases per 100,000\", option =\"plasma\", begin = 0, end = 1, direction = 1) +\n theme_minimal() + \n labs(title= paste(\" \"), x=\"Date\", y=\"Upper Tier Authority Area\") +\n theme(legend.position = \"bottom\") +\n theme(legend.title = element_text(size=15)) +\n theme(axis.text.y = element_text(size=10)) +\n theme(axis.text.x = element_text(size=15)) +\n theme(axis.title=element_text(size=20, face=\"plain\")) +\n theme(legend.key.width = unit(5, \"cm\"), legend.key.height = unit(2, \"cm\"))\n\n\n\n\n\n\n10.5.1.4 Interactive Plots\nInteractive visualisations comprise very effective ways to understand spatio-temporal data and they are now fairly accessible. Interactive visualisations allow for a more data-immersive experience, and enable exploration of the data without having to resort to scripting. Here is when the use of tmap shines as it does not only enables easily creating nice static maps but also interactive maps! Below an interactive map for a time snapshot of the data (i.e. 2020-04-14) is produced, but with a bit of work layers can be added to display multiple temporal slices of the data.\n\n# map\nlegend_title = expression(\"Cumulative Cases per 100,000 Population\")\nimap = tm_shape(dplyr::filter(covid19_spt[,c(\"ctyu19nm\", \"date\", \"c_covid19_r\")], as.character(date) == \"2020-04-14\"), labels = \"Area.name\") +\n tm_fill(\"c_covid19_r\", title = legend_title, palette = magma(256), style =\"cont\", legend.is.portrait=FALSE, alpha = 0.7) +\n tm_borders(col = \"white\") +\n #tm_text(\"ctyu19nm\", size = .4) +\n tm_layout(bg.color = \"white\", # change background colour\n legend.outside = TRUE, # legend outside\n legend.title.size = 1,\n legend.width = 1) \n\nTo view the map on your local machines, execute the code chunk below removing the # sign.\n\n#tmap_mode(\"view\")\n#imap\n\nAlternative data visualisation tools are animations, telliscope and shiny. Animations can be constructed by plotting spatial data frame-by-frame, and then stringing them together in sequence. A useful R packages gganimate and tmap! See Lovelace, Nowosad, and Muenchow (2019). Note that the creation of animations may require external dependencies; hence, they have been included here. Both telliscope and shiny are useful ways for visualising large spatio-temporal data sets in an interactive ways. Some effort is required to deploy these tools.\n\n\n\n10.5.2 Exploratory Analysis\nIn addition to visualising data, we often want to obtain numerical summaries of the data. Again, innovative ways to reduce the inherent dimensionality of the data and examine dependence structures and potential relationships in time and space are needed. We consider visualisations of empirical spatial and temporal means, dependence structures and some basic time-series analysis.\n\n10.5.2.1 Means\nEmpirical Spatial Mean\nThe empirical spatial mean for a data set can be obtained by averaging over time points for one location. In our case, we can compute the empirical spatial mean by averaging the daily rate of new COVID-19 cases for UTLAs between January 30th and April 21st. It reveals that Brent, Southwark and Sunderland report an average daily infection rate of over 5 new cases per 100,000 people, whereas Rutland and Isle of Wight display an average of less than 1.\n\n# compute empirical spatial mean\nsp_av <- covid19_spt %>% group_by(ctyu19nm) %>% # group by spatial unit\n summarise(sp_mu_emp = mean(n_covid19_r))\n\n# plot empirical spatial mean\nggplot(data=sp_av) +\n geom_col( aes( y = reorder(ctyu19nm, sp_mu_emp), x = sp_mu_emp) , fill = \"grey50\") +\n theme_classic() +\n labs(title= paste(\" \"), x=\"Average New Cases per 100,000\", y=\"Upper Tier Authority Area\") +\n theme(legend.position = \"bottom\") +\n theme(axis.text.y = element_text(size=7)) +\n theme(axis.text.x = element_text(size=12)) +\n theme(axis.title=element_text(size=20, face=\"plain\"))\n\n\n\n\nEmpirical Temporal Mean\nThe empirical temporal mean for a data set can be obtained by averaging across spatial locations for a time point. In our case, we can compute the empirical temporal mean by averaging the rate of new COVID-19 cases over UTLAs by day. The empirical temporal mean is plotted below revealing a peak of 8.32 number of new cases per 100,000 people the 7th of April, steadily decreasing to 0.35 for the last reporting observation in our data; that is, April 21st.\n\nNote the empirical temporal mean is smoothed via local polynomial regression fitting; hence below zero values are reported between February and March.\n\n\n# compute temporal mean\ntm_av <- covid19 %>% group_by(date) %>%\n summarise(tm_mu_emp = mean(n_covid19_r))\n\n# plot temporal mean + trends for all spatial units\nggplot() +\n geom_line(data = covid19, mapping = aes(x =date, y = n_covid19_r,\n group = Area.name), color = \"gray80\") +\n theme_classic() +\n geom_smooth(data = tm_av, mapping = aes(x =date, y = tm_mu_emp), \n alpha = 0.5,\n se = FALSE) +\n labs(title= paste(\" \"), x=\"Date\", y=\"Cumulative Cases per 100,000\") +\n theme_classic() +\n theme(plot.title=element_text(size = 18)) +\n theme(axis.text=element_text(size=14)) +\n theme(axis.title.y = element_text(size = 16)) +\n theme(axis.title.x = element_text(size = 16)) +\n theme(plot.subtitle=element_text(size = 16)) +\n theme(axis.title=element_text(size=18, face=\"plain\"))\n\n\n\n\n\n\n10.5.2.2 Dependence\nSpatial Dependence\nAs we know spatial dependence refers to the spatial relationship of a variable’s values for a pairs of locations at a certain distance apart, so that are more similar (or less similar) than expected for randomly associated pairs of observations. Patterns of spatial dependence may change over time. In the case of a disease outbreak patterns of spatial dependence can change very quickly as new cases emerge and social distancing measures are implemented. Chapter 6 illustrates how to measure spatial dependence in the context of spatial data.\n\nChallenge 1: Measure how spatial dependence change over time. Hint: compute the Moran’s I on the rate of new COVID-19 cases (i.e. n_covid19_r in the covid19 data frame) at multiple time points.\n\n\nNote: recall that the problem of ignoring the dependence in the errors when doing OLS regression is that the resulting standard errors and prediction standard errors are inappropriate. In the case of positive dependence, which is the most common case in spatio-temporal data (recall Tobler’s law), the standard errors and prediction standard errors are underestimated. This is if dependence is ignored, resulting in a false sense of how good the estimates and predictions really are.\n\nTemporal Dependence\nAs for spatial data, dependence can also exists in temporal data. Temporal dependence or temporal autocorrelation exists when a variable’s value at time \\(t\\) is dependent on its value(s) at \\(t-1\\). More recent observations are often expected to have a greater influence on present observations. A key difference between temporal and spatial dependence is that temporal dependence is unidirectional (i.e. past observations can only affect present or future observations but not inversely), while spatial dependence is multidirectional (i.e. an observation in a spatial unit can influence and be influenced by observations in multiple spatial units).\nBefore measuring the temporal dependence is our time-series, a time-series object needs to be created with a time stamp and given cycle frequency. A cycle frequency refers to when a seasonal pattern is repeated. We consider a time series of the total number of new COVID-19 cases per 100,000 (i.e. we sum cases over UTLAs by day) and the frequency set to 7 to reflect weekly cycles. So we end up with a data frame of length 71.\n\n# create a time series object\ntotal_cnt <- covid19 %>% group_by(date) %>%\n summarise(new_cases = sum(n_covid19_r)) \ntotal_cases_ts <- ts(total_cnt$new_cases, \n start = 1,\n frequency =7)\n\nThere are various ways to test for temporal autocorrelation. An easy way is to compute the correlation coefficient between a time series measured at time \\(t\\) and its lag measured at time \\(t-1\\). Below we measure the temporal autocorrelation in the rate of new COVID-19 cases per 100,000 people. A correlation of 0.97 is returned indicating high positive autocorrelation; that is, high (low) past numbers of new COVID-19 cases per 100,000 people tend to correlate with higher (lower) present numbers of new COVID-19 cases. The Durbin-Watson test is often used to test for autocorrelation in regression models.\n\n# create lag term t-1\nlag_new_cases <- total_cnt$new_cases[-1]\ntotal_cnt <- cbind(total_cnt[1:70,], lag_new_cases)\ncor(total_cnt[,2:3])\n\n new_cases lag_new_cases\nnew_cases 1.000000 0.974284\nlag_new_cases 0.974284 1.000000\n\n\nTime Series Components\nIn addition to temporal autocorrelation, critical to the analysis of time-series are its constituent components. A time-series is generally defined by three key components:\n\nTrend: A trend exists when there is a long-term increase or decrease in the data.\nSeasonal: A seasonal pattern exists when a time series is affected by seasonal factors and is of a fixed and known frequency. Seasonal cycles can occur at various time intervals such as the time of the day or the time of the year.\nCyclic (random): A cycle exists when the data exhibit rises and falls that are not of a fixed frequency.\n\nTo understand and model a time series, these components need to be identified and appropriately incorporated into a regression model. We illustrate these components by decomposing our time series for total COVID-19 cases below. The top plot shows the observed data. Subsequent plots display the trend, seasonal and random components of the total number of COVID-19 cases on a weekly periodicity. They reveal a clear inverted U-shape trend and seasonal pattern. This idea that we can decompose data to extract information and understand temporal processes is key to understand the concept of basis functions to model spatio-temporal data, which will be introduced in the next section.\n\n# decompose time series\ndec_ts <- decompose(total_cases_ts)\n# plot time series components\nplot(dec_ts)\n\n\n\n\nFor a good introduction to time-series analysis in R, refer to Hyndman and Athanasopoulos (2018) and DataCamp." + "section": "\n10.5 Exploring Spatio-Temporal Data", + "text": "10.5 Exploring Spatio-Temporal Data\nWe now have all the required data in place. In this section various methods of data visualisation are illustrated before key dimensions of the data are explored. Both of these types of exploration can be challenging as one or more dimensions in space and one in time need to be interrogated.\n\n10.5.1 Visualisation\nIn the context spatio-temporal data, a first challenge is data visualization. Visualising more than two dimensions of spatio-temporal data, so it is helpful to slice or aggregate the data over a dimension, use color, or build animations through time. Before exploring the data, we need to define our key variable of interest; that is, the number of confirmed COVID-19 cases per 100,000 people. We also compute the cumulative number of confirmed COVID-19 cases per 100,000 people as it may be handy in some analyses.\nFisrt create variable to be analysed:\n\n# rate of new covid-19 infection\ncovid19_spt$n_covid19_r <- round( (covid19_spt$Daily.lab.confirmed.cases / covid19_spt$Residents) * 100000)\ncovid19$n_covid19_r <- round( (covid19$Daily.lab.confirmed.cases / covid19$Residents) * 100000 )\n\n# risk of cumulative covid-19 infection\ncovid19_spt$c_covid19_r <- round( (covid19_spt$Cumulative.lab.confirmed.cases / covid19_spt$Residents) * 100000)\ncovid19$c_covid19_r <- round( (covid19$Cumulative.lab.confirmed.cases / covid19$Residents) * 100000)\n\n\n10.5.1.1 Spatial Plots\nOne way to visualise the data is using spatial plots; that is, snapshots of a geographic process for a given time period. Data can be mapped in different ways using clorepleth, countour or surface plots. The key aim of these maps is to understand how the overall extent of spatial variation and local patterns of spatial concentration change over time. Below we visualise the weekly number of confirmed COVID-19 cases per 100,000 people.\n\nNote that Weeks range from 5 to 16 as they refer to calendar weeks. Calendar week 5 is when the first COVID-19 case in England was reported.\n\n\n# create data frame for new cases by week\ndaycases_week <- covid19_spt %>% \n group_by(week, ctyu19nm, \n as.character(cty19c), \n Residents) %>%\n summarise(n_daycases = sum(Daily.lab.confirmed.cases)) \n\n# weekly rate of new covid-19 infection\ndaycases_week$wn_covid19_r <- (daycases_week$n_daycases / daycases_week$Residents) * 100000\n\n# map\nlegend_title = expression(\"Cumulative Cases per 100,000 Population\")\ntm_shape(daycases_week) +\n tm_fill(\"wn_covid19_r\", title = legend_title, palette = magma(256), style =\"cont\", legend.hist=FALSE, legend.is.portrait=FALSE) +\n tm_facets(by = \"week\", ncol = 4) +\n tm_borders(col = \"white\", lwd = .1) + # add borders +\n tm_layout(bg.color = \"white\", # change background colour\n legend.outside = TRUE, # legend outside\n legend.outside.position = \"bottom\",\n legend.stack = \"horizontal\",\n legend.title.size = 2,\n legend.width = 1,\n legend.height = 1,\n panel.label.size = 3,\n main.title = \"New COVID-19 Cases by Calendar Week, UTLA, England\") \n\nWarning in pre_process_gt(x, interactive = interactive, orig_crs =\ngm$shape.orig_crs): legend.width controls the width of the legend within a map.\nPlease use legend.outside.size to control the width of the outside legend\n\n\n\n\n\n\n\n\nThe series of maps reveal a stable pattern of low reported cases from calendar weeks 5 to 11. From week 12 a number of hot spots emerged, notably in London, Birmingham, Cumbria and subsequently around Liverpool. The intensity of new cases seem to have started to decline from week 15; yet, it is important to note that week 16 display reported cases for only two days.\n\n10.5.1.2 Time-Series Plots\nTime-series plots can be used to capture a different dimension of the process in analysis. They can be used to better understand changes in an observation location, an aggregation of observations, or multiple locations simultaneously over time. We plot the cumulative number of COVID-19 cases per 100,000 people for UTLAs reporting over 310 cases. The plots identify the UTLAs in London, Newcastle and Sheffield reporting the largest numbers of COVID-19 cases. The plots also reveal that there has been a steady increase in the number of cases, with some differences. While cases have steadily increase in Brent and Southwark since mid March, the rise has been more sudden in Sunderland. The plots also reveal a possible case of misreporting in Sutton towards the end of the series.\n\ntsp <- ggplot(data = covid19_spt,\n mapping = aes(x = date, y = c_covid19_r,\n group = ctyu19nm))\ntsp + geom_line(color = \"blue\") + \n gghighlight(max(c_covid19_r) > 310, use_direct_label = FALSE) +\n labs(title= paste(\" \"), x=\"Date\", y=\"Cumulative Cases per 100,000\") +\n theme_classic() +\n theme(plot.title=element_text(size = 20)) +\n theme(axis.text=element_text(size=16)) +\n theme(axis.title.y = element_text(size = 18)) +\n theme(axis.title.x = element_text(size = 18)) +\n theme(plot.subtitle=element_text(size = 16)) +\n theme(axis.title=element_text(size=20, face=\"plain\")) +\n facet_wrap(~ ctyu19nm)\n\n\n\n\n\n\n\n\n10.5.1.3 Hovmöller Plots\nAn alternative visualisation is a Hovmöller plot - sometimes known as heatmap. It is a two-dimensional space-time representation in which space is collapsed onto one dimension against time. Hovmöller plots can easily be generated if the data are arranged on a space-time grid; however, this is rarely the case. Luckily we have ggplot! which can do magic rearranging the data as needed. Below we produce a Hovmöller plot for UTLAs with resident populations over 260,000. The plot makes clear that the critical period of COVID-19 spread has been during April despite the implementation of a series of social distancing measures by the government.\n\nggplot(data = dplyr::filter(covid19_spt, Residents > 260000), \n mapping = aes(x= date, y= reorder(ctyu19nm, c_covid19_r), fill= c_covid19_r)) +\n geom_tile() +\n scale_fill_viridis(name=\"New Cases per 100,000\", option =\"plasma\", begin = 0, end = 1, direction = 1) +\n theme_minimal() + \n labs(title= paste(\" \"), x=\"Date\", y=\"Upper Tier Authority Area\") +\n theme(legend.position = \"bottom\") +\n theme(legend.title = element_text(size=15)) +\n theme(axis.text.y = element_text(size=10)) +\n theme(axis.text.x = element_text(size=15)) +\n theme(axis.title=element_text(size=20, face=\"plain\")) +\n theme(legend.key.width = unit(5, \"cm\"), legend.key.height = unit(2, \"cm\"))\n\n\n\n\n\n\n\n\n10.5.1.4 Interactive Plots\nInteractive visualisations comprise very effective ways to understand spatio-temporal data and they are now fairly accessible. Interactive visualisations allow for a more data-immersive experience, and enable exploration of the data without having to resort to scripting. Here is when the use of tmap shines as it does not only enables easily creating nice static maps but also interactive maps! Below an interactive map for a time snapshot of the data (i.e. 2020-04-14) is produced, but with a bit of work layers can be added to display multiple temporal slices of the data.\n\n# map\nlegend_title = expression(\"Cumulative Cases per 100,000 Population\")\nimap = tm_shape(dplyr::filter(covid19_spt[,c(\"ctyu19nm\", \"date\", \"c_covid19_r\")], as.character(date) == \"2020-04-14\"), labels = \"Area.name\") +\n tm_fill(\"c_covid19_r\", title = legend_title, palette = magma(256), style =\"cont\", legend.is.portrait=FALSE, alpha = 0.7) +\n tm_borders(col = \"white\") +\n #tm_text(\"ctyu19nm\", size = .4) +\n tm_layout(bg.color = \"white\", # change background colour\n legend.outside = TRUE, # legend outside\n legend.title.size = 1,\n legend.width = 1) \n\nTo view the map on your local machines, execute the code chunk below removing the # sign.\n\n#tmap_mode(\"view\")\n#imap\n\nAlternative data visualisation tools are animations, telliscope and shiny. Animations can be constructed by plotting spatial data frame-by-frame, and then stringing them together in sequence. A useful R packages gganimate and tmap! See Lovelace, Nowosad, and Muenchow (2019). Note that the creation of animations may require external dependencies; hence, they have been included here. Both telliscope and shiny are useful ways for visualising large spatio-temporal data sets in an interactive ways. Some effort is required to deploy these tools.\n\n10.5.2 Exploratory Analysis\nIn addition to visualising data, we often want to obtain numerical summaries of the data. Again, innovative ways to reduce the inherent dimensionality of the data and examine dependence structures and potential relationships in time and space are needed. We consider visualisations of empirical spatial and temporal means, dependence structures and some basic time-series analysis.\n\n10.5.2.1 Means\nEmpirical Spatial Mean\nThe empirical spatial mean for a data set can be obtained by averaging over time points for one location. In our case, we can compute the empirical spatial mean by averaging the daily rate of new COVID-19 cases for UTLAs between January 30th and April 21st. It reveals that Brent, Southwark and Sunderland report an average daily infection rate of over 5 new cases per 100,000 people, whereas Rutland and Isle of Wight display an average of less than 1.\n\n# compute empirical spatial mean\nsp_av <- covid19_spt %>% group_by(ctyu19nm) %>% # group by spatial unit\n summarise(sp_mu_emp = mean(n_covid19_r))\n\n# plot empirical spatial mean\nggplot(data=sp_av) +\n geom_col( aes( y = reorder(ctyu19nm, sp_mu_emp), x = sp_mu_emp) , fill = \"grey50\") +\n theme_classic() +\n labs(title= paste(\" \"), x=\"Average New Cases per 100,000\", y=\"Upper Tier Authority Area\") +\n theme(legend.position = \"bottom\") +\n theme(axis.text.y = element_text(size=7)) +\n theme(axis.text.x = element_text(size=12)) +\n theme(axis.title=element_text(size=20, face=\"plain\"))\n\n\n\n\n\n\n\nEmpirical Temporal Mean\nThe empirical temporal mean for a data set can be obtained by averaging across spatial locations for a time point. In our case, we can compute the empirical temporal mean by averaging the rate of new COVID-19 cases over UTLAs by day. The empirical temporal mean is plotted below revealing a peak of 8.32 number of new cases per 100,000 people the 7th of April, steadily decreasing to 0.35 for the last reporting observation in our data; that is, April 21st.\n\nNote the empirical temporal mean is smoothed via local polynomial regression fitting; hence below zero values are reported between February and March.\n\n\n# compute temporal mean\ntm_av <- covid19 %>% group_by(date) %>%\n summarise(tm_mu_emp = mean(n_covid19_r))\n\n# plot temporal mean + trends for all spatial units\nggplot() +\n geom_line(data = covid19, mapping = aes(x =date, y = n_covid19_r,\n group = Area.name), color = \"gray80\") +\n theme_classic() +\n geom_smooth(data = tm_av, mapping = aes(x =date, y = tm_mu_emp), \n alpha = 0.5,\n se = FALSE) +\n labs(title= paste(\" \"), x=\"Date\", y=\"Cumulative Cases per 100,000\") +\n theme_classic() +\n theme(plot.title=element_text(size = 18)) +\n theme(axis.text=element_text(size=14)) +\n theme(axis.title.y = element_text(size = 16)) +\n theme(axis.title.x = element_text(size = 16)) +\n theme(plot.subtitle=element_text(size = 16)) +\n theme(axis.title=element_text(size=18, face=\"plain\"))\n\n\n\n\n\n\n\n\n10.5.2.2 Dependence\nSpatial Dependence\nAs we know spatial dependence refers to the spatial relationship of a variable’s values for a pairs of locations at a certain distance apart, so that are more similar (or less similar) than expected for randomly associated pairs of observations. Patterns of spatial dependence may change over time. In the case of a disease outbreak patterns of spatial dependence can change very quickly as new cases emerge and social distancing measures are implemented. Chapter 6 illustrates how to measure spatial dependence in the context of spatial data.\n\nChallenge 1: Measure how spatial dependence change over time. Hint: compute the Moran’s I on the rate of new COVID-19 cases (i.e. n_covid19_r in the covid19 data frame) at multiple time points.\n\n\nNote: recall that the problem of ignoring the dependence in the errors when doing OLS regression is that the resulting standard errors and prediction standard errors are inappropriate. In the case of positive dependence, which is the most common case in spatio-temporal data (recall Tobler’s law), the standard errors and prediction standard errors are underestimated. This is if dependence is ignored, resulting in a false sense of how good the estimates and predictions really are.\n\nTemporal Dependence\nAs for spatial data, dependence can also exists in temporal data. Temporal dependence or temporal autocorrelation exists when a variable’s value at time \\(t\\) is dependent on its value(s) at \\(t-1\\). More recent observations are often expected to have a greater influence on present observations. A key difference between temporal and spatial dependence is that temporal dependence is unidirectional (i.e. past observations can only affect present or future observations but not inversely), while spatial dependence is multidirectional (i.e. an observation in a spatial unit can influence and be influenced by observations in multiple spatial units).\nBefore measuring the temporal dependence is our time-series, a time-series object needs to be created with a time stamp and given cycle frequency. A cycle frequency refers to when a seasonal pattern is repeated. We consider a time series of the total number of new COVID-19 cases per 100,000 (i.e. we sum cases over UTLAs by day) and the frequency set to 7 to reflect weekly cycles. So we end up with a data frame of length 71.\n\n# create a time series object\ntotal_cnt <- covid19 %>% group_by(date) %>%\n summarise(new_cases = sum(n_covid19_r)) \ntotal_cases_ts <- ts(total_cnt$new_cases, \n start = 1,\n frequency =7)\n\nThere are various ways to test for temporal autocorrelation. An easy way is to compute the correlation coefficient between a time series measured at time \\(t\\) and its lag measured at time \\(t-1\\). Below we measure the temporal autocorrelation in the rate of new COVID-19 cases per 100,000 people. A correlation of 0.97 is returned indicating high positive autocorrelation; that is, high (low) past numbers of new COVID-19 cases per 100,000 people tend to correlate with higher (lower) present numbers of new COVID-19 cases. The Durbin-Watson test is often used to test for autocorrelation in regression models.\n\n# create lag term t-1\nlag_new_cases <- total_cnt$new_cases[-1]\ntotal_cnt <- cbind(total_cnt[1:70,], lag_new_cases)\ncor(total_cnt[,2:3])\n\n new_cases lag_new_cases\nnew_cases 1.000000 0.974284\nlag_new_cases 0.974284 1.000000\n\n\nTime Series Components\nIn addition to temporal autocorrelation, critical to the analysis of time-series are its constituent components. A time-series is generally defined by three key components:\n\nTrend: A trend exists when there is a long-term increase or decrease in the data.\nSeasonal: A seasonal pattern exists when a time series is affected by seasonal factors and is of a fixed and known frequency. Seasonal cycles can occur at various time intervals such as the time of the day or the time of the year.\nCyclic (random): A cycle exists when the data exhibit rises and falls that are not of a fixed frequency.\n\nTo understand and model a time series, these components need to be identified and appropriately incorporated into a regression model. We illustrate these components by decomposing our time series for total COVID-19 cases below. The top plot shows the observed data. Subsequent plots display the trend, seasonal and random components of the total number of COVID-19 cases on a weekly periodicity. They reveal a clear inverted U-shape trend and seasonal pattern. This idea that we can decompose data to extract information and understand temporal processes is key to understand the concept of basis functions to model spatio-temporal data, which will be introduced in the next section.\n\n# decompose time series\ndec_ts <- decompose(total_cases_ts)\n# plot time series components\nplot(dec_ts)\n\n\n\n\n\n\n\nFor a good introduction to time-series analysis in R, refer to Hyndman and Athanasopoulos (2018) and DataCamp.", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#spatio-temporal-data-modelling", "href": "10-st_analysis.html#spatio-temporal-data-modelling", "title": "10  Spatio-Temporal Analysis", - "section": "10.6 Spatio-Temporal Data Modelling", - "text": "10.6 Spatio-Temporal Data Modelling\nHaving some understanding of the spatio-temporal patterns of COVID-19 spread through data exploration, we are ready to start further examining structural relationships between the rate of new infections and local contextual factors via regression modelling across UTLAs. Specifically we consider the number of new cases per 100,000 people to capture the rate of new infections and only one contextual factor; that is, the share of population suffering from long-term sickness or disabled. We will consider some basic statistical models, of the form of linear regression and generalized linear models, to account for spatio-temporal dependencies in the data. Note that we do not consider more complex structures based on hierarchical models or spatio-temporal weighted regression models which would be the natural step moving forward.\nAs any modelling approach, spatio-temporal statistical modelling has three principal goals:\n\npredicting values of a given outcome variable at some location in space within the time span of the observations and offering information about the uncertainty of those predictions;\nperforming statistical inference about the influence of predictors on an outcome variable in the presence of spatio-temporal dependence; and,\nforecasting future values of an outcome variable at some location, offering information about the uncertainty of the forecast.\n\n\n10.6.1 Intuition\nThe key idea on what follows is to use a basic statistical regression model to understand the relationship between the share of new COVID-19 infections and the share of population suffering from long-term illness, accounting for spatio-temporal dependencies. We will consider what is known as a trend-surface regression model which assumes that spatio-temporal dependencies can be accounted for by “trend” components and incorporate as predictors in the model. Formally we consider the regression model below which seeks to account for spatial and temporal trends.\n\\[y(s_{i}, t_{j}) = \\beta_{0} + \\beta_{k}x(s_{i}, t_{j}) + e(s_{i}, t_{j})\\]\nwhere \\(\\beta_{0}\\) is the intercept and \\(\\beta_{k}\\) represents a set of regression coefficients associated with \\(x(s_{i}, t_{j})\\); the \\(k\\) indicates the number of covariates at spatial location \\(s_{i}\\) and time \\(t_{j}\\); \\(e\\) represents the regression errors which are assumed to follow a normal distribution. The key difference to aproaches considered in previous chapters is the incorporation of space and time together. As we learnt from the previous section, this has implications are we now have two sources of dependence: spatial and temporal autocorrelation, as well as seasonal and trend components. This has implications for modelling as we now need to account for all of these components if we are to establish any relationship between \\(y\\) and \\(x\\).\nA key implication is how we consider the set of covariates represented by \\(x\\). Three key types can be identified:\n\nspatial-variant, temporal-invariant covariates: these are attributes which may vary across space but be temporally invariant, such as geographical distances;\nspatial-invariant, temporal-variant covariates: these are attributes which do not vary across space but change over time; and,\nspatial-variant, temporal-variant covariates: these are attributes which vary over both space and time;\n\n\nNote that what is variant or invariant will depend on the spatial and temporal scale of the analysis.\n\nWe can also consider spatio-temporal “basis functions”. Note that this is an important concept for the rest of the Chapter. What are basis functions then? If you think that spatio-temporal data represent a complex set of curves or surfaces in space, basis functions represent the components into which this set of curves can be decomposed. In this sense, basis functions operate in a similar fashion as the decomposition of time series considered above i.e. time series data can be decomposed into a trend, seasonal and random components and their sum can be used to represent the observed temporal trajectory. Basis functions offer an effective way to incorporate spatio-temporal dependencies. Thus, basis functions have the key goal of accounting for spatio-temporal dependencies as spatial weight matrices or temporal lags help accounting spatial autocorrelation in spatial models and temporal autocorrelation in time series analysis.\nAs standard regression coefficients, basis functions are related to \\(y\\) via coefficients (or weights). The difference is that we typically assume that basis functions are known while coefficients are random. Examples of basis functions include polynomials, splines, wavelets, sines and cosines so various linear combinations that can be used to infer potential spatio-temporal dependencies in the data. This is similar to deep learning models in which cases you provide, for example, an image and the model provides a classification of pixels. But you normally do not know what the classification represents (hence they are known as black boxes!) so further analysis on the classification is needed to understand what the model has attempted to capture. Basically basis functions are smoother functions to represent the observed data, and their objective to capture the spatial and temporal variability in the data as well as their dependence.\nFor our application, we start by considering a basic OLS regression model with the following basis functions to account spatial-temporal structures:\n\noverall mean;\nlinear in lon-coordinate;\nlinear in lat-coordinate;\nlinear time daily trend;\nadditional spatio-temporal basis functions which are presented below; and,\n\nThese basis functions are incorporated as independent variables in the regression model. Additionally, we also include the share of population suffering from long-term illness as we know it is highly correlated to the cumulative number of COVID-19 cases. The share of population suffering long-term illness is incorporated as a spatial-variant, temporal-invariant covariates given that rely in 2011 census data.\n\n\n10.6.2 Fitting Spatio-Temporal Models\nAs indicated at the start of this Chapter, we use the FRK framework developed by Cressie and Johannesson (2008). It provides a scalable, relies on the use a spatial random effects model (with which we have some familiarity) and can be easily implemented in R by the use of the FRK package (Zammit-Mangion and Cressie 2017). In this framework, a spatially correlated errors can be decomposed using a linear combination of spatial basis functions, effectively addressing issues of spatial-temporal dependence and nonstationarity. The specification of spatio-temporal basis functions is a key component of the model and they can be generated automatically or by the user via the FRK package. We will use the automatically generated functions. While as we will notice they are difficult to interpret, user generated functions require greater understanding of the spatio-temporal structure of COVID-19 which is beyond the scope of this Chapter.\nPrepare Data\nThe first step to create a data frame with the variables that we will consider for the analysis. We first remove the geometries to convert covid19_spt from a simple feature object to a data frame and then compute the share of long-term illness population.\n\n# remove geometries\nst_geometry(covid19_spt) <- NULL\n\n# share of population in long-term illness \ncovid19_spt <- covid19_spt %>% mutate(\n lt_illness = Longterm_sick_or_disabled / Residents\n)\n\nConstruct Basis Functions\nWe now build the set of basis functions. The can be constructed by using the function auto_basis from the FRK package. The function takes as arguments: data, nres (which is the number of “resolutions” or aggregation to construct); and type of basis function to use. We consider a single resolution of the default Gaussian radial basis function.\n\n# build basis functions\nG <- auto_basis(data = covid19_spt[,c(\"long\",\"lat\")] %>%\n SpatialPoints(), # To sp obj\n nres = 1, # One resolution\n type = \"Gaussian\") # Gaussian BFs\n# basis functions evaluated at data locations are then the covariates\nS <- eval_basis(basis = G, # basis functions\n s = covid19_spt[,c(\"long\",\"lat\")] %>%\n as.matrix()) %>% # conv. to matrix\n as.matrix() # conv. to matrix\ncolnames(S) <- paste0(\"B\", 1:ncol(S)) # assign column names\n\nAdd Basis Functions to Data Frame\nWe then prepare a data frame for the regression model, adding the weights extracted from the basis functions. These weights enter as covariates in our model. Note that the resulting number of basis functions is nine. Explore by executing colnames(S). Below we select only relevant variables for our model.\n\n# selecting variables\nreg_df <- cbind(covid19_spt, S) %>%\n dplyr::select(ctyu19nm, n_covid19_r, long, lat, date, lt_illness, B1:B9)\n\nFit Linear Regression\nWe now fit a linear model using lm including as covariates longitude, latitude, day, share of long-term ill population and the nine basis functions.\n\nRecall that latitude refers to north/south from the equator and longitude refers to west/east from Greenwich. Further up north means a higher latitude score. Further west means higher longitude score. Scores for Liverpool (53.4084° N, 2.9916° W) are thus higher than for London (51.5074° N, 0.1278° W). This will be helpful for interpretation.\n\n\neq1 <- n_covid19_r ~ long + lat + date + lt_illness + .\nlm_m <- lm(formula = eq1, \n data = dplyr::select(reg_df, -ctyu19nm))\nlm_m %>% summary()\n\n\nCall:\nlm(formula = eq1, data = dplyr::select(reg_df, -ctyu19nm))\n\nResiduals:\n Min 1Q Median 3Q Max \n-7.9726 -1.6679 -0.4867 1.1702 22.5346 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -2.082e+03 2.839e+01 -73.354 < 2e-16 ***\nlong -1.932e+00 3.620e-01 -5.336 9.7e-08 ***\nlat 3.390e+00 3.266e-01 10.380 < 2e-16 ***\ndate 1.033e-01 1.245e-03 82.958 < 2e-16 ***\nlt_illness 3.276e+01 3.541e+00 9.250 < 2e-16 ***\nB1 7.556e+00 3.157e+00 2.393 0.0167 * \nB2 1.898e+00 1.409e+00 1.347 0.1780 \nB3 1.750e+01 1.978e+00 8.847 < 2e-16 ***\nB4 -2.022e+00 2.742e+00 -0.737 0.4609 \nB5 2.207e+00 2.233e+00 0.989 0.3229 \nB6 -9.814e-01 2.498e+00 -0.393 0.6945 \nB7 -2.031e-01 3.687e+00 -0.055 0.9561 \nB8 -2.234e+00 2.801e+00 -0.797 0.4252 \nB9 1.877e+00 2.543e+00 0.738 0.4604 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.842 on 10636 degrees of freedom\nMultiple R-squared: 0.4169, Adjusted R-squared: 0.4162 \nF-statistic: 585 on 13 and 10636 DF, p-value: < 2.2e-16\n\n\nCoefficients for explicitly specified spatial and temporal variables and the share of long-term ill population are all statistically significant. The interpretation of the regression coefficients is as usual; that is, one unit increase in a covariate relates to one unit increase in the dependent variable. For instance, the coefficient for long-term illness population indicates that UTLAs with a larger share of long-term ill population in one percentage point tend to have 328 more new COVID-19 cases per 100,000 people! on average. The coefficient for date reveals a strong positive temporal dependence with an average increase in the number of new cases per 100,000 people over time. The coefficient for latitude indicates as we move north the number of new COVID-19 cases per 100,000 people tends to be higher but lower if we move west.\nWhile overall the model provides some understanding of the spatio-temporal structure of the spread of COVID-19, the overall fit of the model is relatively poor. The \\(R^{2}\\) reveals that the model explains only 4.2% of the variability of the spread of COVID-19 cases, reflecting the complexity of modelling spatio-temporal processes. Also, except for two, the coefficients associated to the basis functions are statistically insignificant. A key issue that we have ignored so far is the fact that our dependent variable is a count and is highly skewed - refer back to Section [8.4 Exploratory Analysis].\n\nChallenge 2: Explore a model with only spatial components (i.e. long and lat) or only temporal components (day). What model returns the largest \\(R^{2}\\)?\n\nPoisson Regression\nA Poisson regression model provides a more appropriate framework to address these issues. We do this fitting a general linear model (or GLM) specifying the family function to be a Poisson.\n\n# estimate a poisson model\npoisson_m1 <- glm(eq1,\n family = poisson(\"log\"), # Poisson + log link\n data = dplyr::select(reg_df, -ctyu19nm))\npoisson_m1 %>% summary()\n\n\nCall:\nglm(formula = eq1, family = poisson(\"log\"), data = dplyr::select(reg_df, \n -ctyu19nm))\n\nDeviance Residuals: \n Min 1Q Median 3Q Max \n-5.7454 -1.2043 -0.6993 0.3764 7.8981 \n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) -1.027e+03 8.168e+00 -125.699 < 2e-16 ***\nlong -8.534e-01 9.080e-02 -9.399 < 2e-16 ***\nlat 1.456e+00 7.617e-02 19.115 < 2e-16 ***\ndate 5.153e-02 3.871e-04 133.121 < 2e-16 ***\nlt_illness 1.166e+01 7.701e-01 15.139 < 2e-16 ***\nB1 3.418e+00 8.005e-01 4.270 1.96e-05 ***\nB2 4.414e-01 3.249e-01 1.359 0.17421 \nB3 8.531e+00 5.480e-01 15.568 < 2e-16 ***\nB4 -7.129e-01 5.865e-01 -1.215 0.22418 \nB5 1.639e+00 5.048e-01 3.246 0.00117 ** \nB6 -1.282e+00 5.618e-01 -2.283 0.02245 * \nB7 -3.572e-01 8.411e-01 -0.425 0.67102 \nB8 -1.003e+00 6.262e-01 -1.602 0.10917 \nB9 1.655e+00 6.268e-01 2.640 0.00829 ** \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for poisson family taken to be 1)\n\n Null deviance: 51245 on 10649 degrees of freedom\nResidual deviance: 24458 on 10636 degrees of freedom\nAIC: 42364\n\nNumber of Fisher Scoring iterations: 5\n\n\nThe Poisson model seems to provide a more appropriate functional forms to identify the strength of the relationship between new COVID-19 cases and spatio-temporal dependencies as captured by a greater number (relative to the linear model) of basis functions coefficients becoming statistically significant. Yet, the Poisson model assumes that the mean and variance of the COVID-19 cases is the same. But, given the distribution of our dependent variable, its variance is likely to be greater than the mean. That means the data exhibit “overdispersion”. How do we know this? An estimate of the dispersion is given by the ratio of the deviance to the total degrees of freedom (the number of data points minus the number of covariates). In this case the dispersion estimate is:\n\npoisson_m1$deviance / poisson_m1$df.residual\n\n[1] 2.29953\n\n\nwhich is clearly greater than 1! i.e. the data are overdispersed.\nQuasipoisson Regression\nAn approach to account for overdispersion is to use quasipoisson when calling glm. The quasi-Poisson model assumes that the variance is proportional to the mean, and that the constant of the proportionality is the over-dispersion parameter. This model corrects the standard error of the estimated coefficients. So only p-values and t values are affected. No changes are recorded in terms of residual deviance (24458) and median of deviance residuals (-0.6993), compared to the standard Poisson model.\n\n# estimate a quasipoisson model\nqpoisson_m1 <- glm(eq1,\n family = quasipoisson(\"log\"), # QuasiPoisson + log link\n data = dplyr::select(reg_df, -ctyu19nm))\nqpoisson_m1 %>% summary()\n\n\nCall:\nglm(formula = eq1, family = quasipoisson(\"log\"), data = dplyr::select(reg_df, \n -ctyu19nm))\n\nDeviance Residuals: \n Min 1Q Median 3Q Max \n-5.7454 -1.2043 -0.6993 0.3764 7.8981 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.027e+03 1.215e+01 -84.490 < 2e-16 ***\nlong -8.534e-01 1.351e-01 -6.318 2.76e-10 ***\nlat 1.456e+00 1.133e-01 12.848 < 2e-16 ***\ndate 5.153e-02 5.759e-04 89.478 < 2e-16 ***\nlt_illness 1.166e+01 1.146e+00 10.176 < 2e-16 ***\nB1 3.418e+00 1.191e+00 2.870 0.00411 ** \nB2 4.414e-01 4.833e-01 0.913 0.36109 \nB3 8.531e+00 8.153e-01 10.464 < 2e-16 ***\nB4 -7.129e-01 8.726e-01 -0.817 0.41395 \nB5 1.639e+00 7.510e-01 2.182 0.02915 * \nB6 -1.282e+00 8.358e-01 -1.534 0.12499 \nB7 -3.572e-01 1.251e+00 -0.286 0.77526 \nB8 -1.003e+00 9.316e-01 -1.077 0.28162 \nB9 1.655e+00 9.325e-01 1.774 0.07603 . \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for quasipoisson family taken to be 2.213379)\n\n Null deviance: 51245 on 10649 degrees of freedom\nResidual deviance: 24458 on 10636 degrees of freedom\nAIC: NA\n\nNumber of Fisher Scoring iterations: 5\n\n\nNegative Binomial Regression\nAn alternative approach to deal with overdispersion is the Negative Binomial Model (NBM). This models relaxes the assumption of equality between the mean and variance. We estimate a NBM by using the function glm.nb from the MASS package.\n\n# estimate a negative binomial model\nnb_m1 <- glm.nb(eq1, \n data = dplyr::select(reg_df, -ctyu19nm))\nnb_m1 %>% summary()\n\n\nCall:\nglm.nb(formula = eq1, data = dplyr::select(reg_df, -ctyu19nm), \n init.theta = 1.493089258, link = log)\n\nDeviance Residuals: \n Min 1Q Median 3Q Max \n-3.0655 -0.8110 -0.4119 0.1861 3.1676 \n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) -1.540e+03 1.596e+01 -96.459 < 2e-16 ***\nlong -8.402e-01 1.650e-01 -5.090 3.57e-07 ***\nlat 1.604e+00 1.456e-01 11.021 < 2e-16 ***\ndate 7.901e-02 7.522e-04 105.030 < 2e-16 ***\nlt_illness 1.440e+01 1.534e+00 9.387 < 2e-16 ***\nB1 5.121e+00 1.460e+00 3.508 0.000452 ***\nB2 1.622e-01 6.177e-01 0.263 0.792897 \nB3 9.502e+00 9.469e-01 10.035 < 2e-16 ***\nB4 -2.054e+00 1.183e+00 -1.736 0.082590 . \nB5 2.461e+00 9.802e-01 2.510 0.012059 * \nB6 -1.095e+00 1.089e+00 -1.005 0.314895 \nB7 6.486e-01 1.642e+00 0.395 0.692877 \nB8 -1.143e+00 1.225e+00 -0.933 0.350789 \nB9 1.068e+00 1.169e+00 0.914 0.360917 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for Negative Binomial(1.4931) family taken to be 1)\n\n Null deviance: 22092.1 on 10649 degrees of freedom\nResidual deviance: 8374.3 on 10636 degrees of freedom\nAIC: 34057\n\nNumber of Fisher Scoring iterations: 1\n\n Theta: 1.4931 \n Std. Err.: 0.0361 \n\n 2 x log-likelihood: -34027.2980 \n\n\nThe NBM leads to a significant reduction in residual deviance from 24,458 returned by the Poisson model to only 8,374. It points to a major improvement in explaining the spatio-temporal variability in the spread of COVID-19\nIncluding Interactions\nYet, we may further refine our model based on a different strategy. Let’s try running a NBM including interaction terms between spatial and temporal terms (i.e. longitude, latitude and date). We can do this by estimating the following model c_covid19_r ~ (long + lat + date)^2 + lt_illness + .\n\n# new model specification\neq2 <- n_covid19_r ~ (long + lat + date)^2 + lt_illness + .\n# estimate a negative binomial model\nnb_m2 <- glm.nb(eq2, \n data = dplyr::select(reg_df, -ctyu19nm))\nnb_m2 %>% summary()\n\n\nCall:\nglm.nb(formula = eq2, data = dplyr::select(reg_df, -ctyu19nm), \n init.theta = 1.56089592, link = log)\n\nDeviance Residuals: \n Min 1Q Median 3Q Max \n-3.1506 -0.8099 -0.4039 0.2036 3.4084 \n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 4.797e+03 6.955e+02 6.897 5.32e-12 ***\nlong -4.509e+01 1.559e+01 -2.892 0.00382 ** \nlat -1.185e+02 1.342e+01 -8.827 < 2e-16 ***\ndate -2.754e-01 3.788e-02 -7.270 3.61e-13 ***\nlt_illness 1.329e+01 1.522e+00 8.734 < 2e-16 ***\nB1 1.155e+01 1.571e+00 7.354 1.92e-13 ***\nB2 -4.181e-01 6.212e-01 -0.673 0.50095 \nB3 2.062e+01 1.408e+00 14.644 < 2e-16 ***\nB4 -6.669e+00 1.256e+00 -5.311 1.09e-07 ***\nB5 9.446e+00 1.170e+00 8.071 6.96e-16 ***\nB6 -1.050e+01 1.398e+00 -7.509 5.96e-14 ***\nB7 2.309e+01 2.626e+00 8.793 < 2e-16 ***\nB8 -5.111e+00 1.279e+00 -3.995 6.48e-05 ***\nB9 1.139e+00 1.159e+00 0.983 0.32575 \nlong:lat 1.574e+00 1.462e-01 10.763 < 2e-16 ***\nlong:date -1.937e-03 7.525e-04 -2.574 0.01005 * \nlat:date 6.713e-03 7.309e-04 9.185 < 2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for Negative Binomial(1.5609) family taken to be 1)\n\n Null deviance: 22557.0 on 10649 degrees of freedom\nResidual deviance: 8352.3 on 10633 degrees of freedom\nAIC: 33849\n\nNumber of Fisher Scoring iterations: 1\n\n Theta: 1.5609 \n Std. Err.: 0.0383 \n\n 2 x log-likelihood: -33813.3960 \n\n\nThis model leads to a slightly better model by returning a small reduction in the residual deviance and AIC score. Interestingly it also returns highly statistically significant coefficients for all three interaction terms between longitude and latitude (long:lat), longitude and date (long:date), and latitude and date (lat:date). The first indicates that as we move one degree north and west, the number of new cases tend to increase in 2 cases. The second indicates that UTLAs located further north of England tend to record a smaller number of cases over time. The third indicates that UTLAs on the west of England tend to report a much higher number of cases as time passes.\nYou can report the output for all models estimated above by executing (after removing #):\n\n# export_summs(lm_m, poisson_m, qpoisson_m1, nb_m1, nb_m2)\n\nNote that you may need to install the huxtable package.\n\n10.6.2.1 Model Comparision\nTo compare regression models based on different specifications and assumptions, like those reported above, you may want to consider the cross-validation approach used in Chapter 5. An alternative approach if you would like to get a quick sense of model fit is to explore the correlation between observed and predicted values of the dependent variable. For our models, we can achieve this by executing:\n\n# computing predictions for all models\nlm_cnt <- predict(lm_m)\npoisson_cnt <- predict(poisson_m1)\nnb1_cnt <- predict(nb_m1)\nnb2_cnt <- predict(nb_m2)\nreg_df <- cbind(reg_df, lm_cnt, poisson_cnt, nb1_cnt, nb2_cnt)\n\n# computing correlation coefficients\ncormat <- cor(reg_df[, c(\"n_covid19_r\", \"lm_cnt\", \"poisson_cnt\", \"nb1_cnt\", \"nb2_cnt\")], \n use=\"complete.obs\", \n method=\"pearson\")\n\n# significance test\nsig1 <- corrplot::cor.mtest(reg_df[, c(\"n_covid19_r\", \"lm_cnt\", \"poisson_cnt\", \"nb1_cnt\", \"nb2_cnt\")],\n conf.level = .95)\n\n# create a correlogram\ncorrplot::corrplot.mixed(cormat,\n number.cex = 1,\n tl.pos = \"d\",\n tl.cex = 0.9)\n\n\n\n\nAll the models do a relatively job at predicting the observed count of new COVID-19 cases. They display correlation coefficients of 0.64/5 and high degree of correlation between them. These models will provide a good starting point for the assignment. There are a potentially few easy ways to make some considerable improvement.\n\nOption 1 Remove all zeros from the dependent variable n_covid19_r. They are likely to be affecting the ability of the model to predict positive values which are of main interest if we want to understand the spatio-temporal patterns of the outbreak.\nOption 2 Remove all zeros from the dependent variable and additionally use its log for the regression model.\nOption 3 Include more explanatory variables. Look for factors which can explain the spatial-temporal variability of the current COVID-19 outbreak. Look for hypotheses / anecdotal evidence from the newspapers and social media.\nOption 4 Check for collinearity. Collinearity is likely to be an issue given the way basis functions are created. Checking for collinearity of course will not improve the fit of the existing model but it is important to remove collinear terms if statistical inference is a key goal - which in this case is. Over to you now!" + "section": "\n10.6 Spatio-Temporal Data Modelling", + "text": "10.6 Spatio-Temporal Data Modelling\nHaving some understanding of the spatio-temporal patterns of COVID-19 spread through data exploration, we are ready to start further examining structural relationships between the rate of new infections and local contextual factors via regression modelling across UTLAs. Specifically we consider the number of new cases per 100,000 people to capture the rate of new infections and only one contextual factor; that is, the share of population suffering from long-term sickness or disabled. We will consider some basic statistical models, of the form of linear regression and generalized linear models, to account for spatio-temporal dependencies in the data. Note that we do not consider more complex structures based on hierarchical models or spatio-temporal weighted regression models which would be the natural step moving forward.\nAs any modelling approach, spatio-temporal statistical modelling has three principal goals:\n\npredicting values of a given outcome variable at some location in space within the time span of the observations and offering information about the uncertainty of those predictions;\nperforming statistical inference about the influence of predictors on an outcome variable in the presence of spatio-temporal dependence; and,\nforecasting future values of an outcome variable at some location, offering information about the uncertainty of the forecast.\n\n\n10.6.1 Intuition\nThe key idea on what follows is to use a basic statistical regression model to understand the relationship between the share of new COVID-19 infections and the share of population suffering from long-term illness, accounting for spatio-temporal dependencies. We will consider what is known as a trend-surface regression model which assumes that spatio-temporal dependencies can be accounted for by “trend” components and incorporate as predictors in the model. Formally we consider the regression model below which seeks to account for spatial and temporal trends.\n\\[y(s_{i}, t_{j}) = \\beta_{0} + \\beta_{k}x(s_{i}, t_{j}) + e(s_{i}, t_{j})\\]\nwhere \\(\\beta_{0}\\) is the intercept and \\(\\beta_{k}\\) represents a set of regression coefficients associated with \\(x(s_{i}, t_{j})\\); the \\(k\\) indicates the number of covariates at spatial location \\(s_{i}\\) and time \\(t_{j}\\); \\(e\\) represents the regression errors which are assumed to follow a normal distribution. The key difference to aproaches considered in previous chapters is the incorporation of space and time together. As we learnt from the previous section, this has implications are we now have two sources of dependence: spatial and temporal autocorrelation, as well as seasonal and trend components. This has implications for modelling as we now need to account for all of these components if we are to establish any relationship between \\(y\\) and \\(x\\).\nA key implication is how we consider the set of covariates represented by \\(x\\). Three key types can be identified:\n\nspatial-variant, temporal-invariant covariates: these are attributes which may vary across space but be temporally invariant, such as geographical distances;\nspatial-invariant, temporal-variant covariates: these are attributes which do not vary across space but change over time; and,\nspatial-variant, temporal-variant covariates: these are attributes which vary over both space and time;\n\n\nNote that what is variant or invariant will depend on the spatial and temporal scale of the analysis.\n\nWe can also consider spatio-temporal “basis functions”. Note that this is an important concept for the rest of the Chapter. What are basis functions then? If you think that spatio-temporal data represent a complex set of curves or surfaces in space, basis functions represent the components into which this set of curves can be decomposed. In this sense, basis functions operate in a similar fashion as the decomposition of time series considered above i.e. time series data can be decomposed into a trend, seasonal and random components and their sum can be used to represent the observed temporal trajectory. Basis functions offer an effective way to incorporate spatio-temporal dependencies. Thus, basis functions have the key goal of accounting for spatio-temporal dependencies as spatial weight matrices or temporal lags help accounting spatial autocorrelation in spatial models and temporal autocorrelation in time series analysis.\nAs standard regression coefficients, basis functions are related to \\(y\\) via coefficients (or weights). The difference is that we typically assume that basis functions are known while coefficients are random. Examples of basis functions include polynomials, splines, wavelets, sines and cosines so various linear combinations that can be used to infer potential spatio-temporal dependencies in the data. This is similar to deep learning models in which cases you provide, for example, an image and the model provides a classification of pixels. But you normally do not know what the classification represents (hence they are known as black boxes!) so further analysis on the classification is needed to understand what the model has attempted to capture. Basically basis functions are smoother functions to represent the observed data, and their objective to capture the spatial and temporal variability in the data as well as their dependence.\nFor our application, we start by considering a basic OLS regression model with the following basis functions to account spatial-temporal structures:\n\noverall mean;\nlinear in lon-coordinate;\nlinear in lat-coordinate;\nlinear time daily trend;\nadditional spatio-temporal basis functions which are presented below; and,\n\nThese basis functions are incorporated as independent variables in the regression model. Additionally, we also include the share of population suffering from long-term illness as we know it is highly correlated to the cumulative number of COVID-19 cases. The share of population suffering long-term illness is incorporated as a spatial-variant, temporal-invariant covariates given that rely in 2011 census data.\n\n10.6.2 Fitting Spatio-Temporal Models\nAs indicated at the start of this Chapter, we use the FRK framework developed by Cressie and Johannesson (2008). It provides a scalable, relies on the use a spatial random effects model (with which we have some familiarity) and can be easily implemented in R by the use of the FRK package (Zammit-Mangion and Cressie 2017). In this framework, a spatially correlated errors can be decomposed using a linear combination of spatial basis functions, effectively addressing issues of spatial-temporal dependence and nonstationarity. The specification of spatio-temporal basis functions is a key component of the model and they can be generated automatically or by the user via the FRK package. We will use the automatically generated functions. While as we will notice they are difficult to interpret, user generated functions require greater understanding of the spatio-temporal structure of COVID-19 which is beyond the scope of this Chapter.\nPrepare Data\nThe first step to create a data frame with the variables that we will consider for the analysis. We first remove the geometries to convert covid19_spt from a simple feature object to a data frame and then compute the share of long-term illness population.\n\n# remove geometries\nst_geometry(covid19_spt) <- NULL\n\n# share of population in long-term illness \ncovid19_spt <- covid19_spt %>% mutate(\n lt_illness = Longterm_sick_or_disabled / Residents\n)\n\nConstruct Basis Functions\nWe now build the set of basis functions. The can be constructed by using the function auto_basis from the FRK package. The function takes as arguments: data, nres (which is the number of “resolutions” or aggregation to construct); and type of basis function to use. We consider a single resolution of the default Gaussian radial basis function.\n\n# build basis functions\nG <- auto_basis(data = covid19_spt[,c(\"long\",\"lat\")] %>%\n SpatialPoints(), # To sp obj\n nres = 1, # One resolution\n type = \"Gaussian\") # Gaussian BFs\n# basis functions evaluated at data locations are then the covariates\nS <- eval_basis(basis = G, # basis functions\n s = covid19_spt[,c(\"long\",\"lat\")] %>%\n as.matrix()) %>% # conv. to matrix\n as.matrix() # conv. to matrix\ncolnames(S) <- paste0(\"B\", 1:ncol(S)) # assign column names\n\nAdd Basis Functions to Data Frame\nWe then prepare a data frame for the regression model, adding the weights extracted from the basis functions. These weights enter as covariates in our model. Note that the resulting number of basis functions is nine. Explore by executing colnames(S). Below we select only relevant variables for our model.\n\n# selecting variables\nreg_df <- cbind(covid19_spt, S) %>%\n dplyr::select(ctyu19nm, n_covid19_r, long, lat, date, lt_illness, B1:B9)\n\nFit Linear Regression\nWe now fit a linear model using lm including as covariates longitude, latitude, day, share of long-term ill population and the nine basis functions.\n\nRecall that latitude refers to north/south from the equator and longitude refers to west/east from Greenwich. Further up north means a higher latitude score. Further west means higher longitude score. Scores for Liverpool (53.4084° N, 2.9916° W) are thus higher than for London (51.5074° N, 0.1278° W). This will be helpful for interpretation.\n\n\neq1 <- n_covid19_r ~ long + lat + date + lt_illness + .\nlm_m <- lm(formula = eq1, \n data = dplyr::select(reg_df, -ctyu19nm))\nlm_m %>% summary()\n\n\nCall:\nlm(formula = eq1, data = dplyr::select(reg_df, -ctyu19nm))\n\nResiduals:\n Min 1Q Median 3Q Max \n-7.9726 -1.6679 -0.4867 1.1702 22.5346 \n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -2.082e+03 2.839e+01 -73.354 < 2e-16 ***\nlong -1.932e+00 3.620e-01 -5.336 9.7e-08 ***\nlat 3.390e+00 3.266e-01 10.380 < 2e-16 ***\ndate 1.033e-01 1.245e-03 82.958 < 2e-16 ***\nlt_illness 3.276e+01 3.541e+00 9.250 < 2e-16 ***\nB1 7.556e+00 3.157e+00 2.393 0.0167 * \nB2 1.898e+00 1.409e+00 1.347 0.1780 \nB3 1.750e+01 1.978e+00 8.847 < 2e-16 ***\nB4 -2.022e+00 2.742e+00 -0.737 0.4609 \nB5 2.207e+00 2.233e+00 0.989 0.3229 \nB6 -9.814e-01 2.498e+00 -0.393 0.6945 \nB7 -2.031e-01 3.687e+00 -0.055 0.9561 \nB8 -2.234e+00 2.801e+00 -0.797 0.4252 \nB9 1.877e+00 2.543e+00 0.738 0.4604 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\nResidual standard error: 2.842 on 10636 degrees of freedom\nMultiple R-squared: 0.4169, Adjusted R-squared: 0.4162 \nF-statistic: 585 on 13 and 10636 DF, p-value: < 2.2e-16\n\n\nCoefficients for explicitly specified spatial and temporal variables and the share of long-term ill population are all statistically significant. The interpretation of the regression coefficients is as usual; that is, one unit increase in a covariate relates to one unit increase in the dependent variable. For instance, the coefficient for long-term illness population indicates that UTLAs with a larger share of long-term ill population in one percentage point tend to have 328 more new COVID-19 cases per 100,000 people! on average. The coefficient for date reveals a strong positive temporal dependence with an average increase in the number of new cases per 100,000 people over time. The coefficient for latitude indicates as we move north the number of new COVID-19 cases per 100,000 people tends to be higher but lower if we move west.\nWhile overall the model provides some understanding of the spatio-temporal structure of the spread of COVID-19, the overall fit of the model is relatively poor. The \\(R^{2}\\) reveals that the model explains only 4.2% of the variability of the spread of COVID-19 cases, reflecting the complexity of modelling spatio-temporal processes. Also, except for two, the coefficients associated to the basis functions are statistically insignificant. A key issue that we have ignored so far is the fact that our dependent variable is a count and is highly skewed - refer back to Section [8.4 Exploratory Analysis].\n\nChallenge 2: Explore a model with only spatial components (i.e. long and lat) or only temporal components (day). What model returns the largest \\(R^{2}\\)?\n\nPoisson Regression\nA Poisson regression model provides a more appropriate framework to address these issues. We do this fitting a general linear model (or GLM) specifying the family function to be a Poisson.\n\n# estimate a poisson model\npoisson_m1 <- glm(eq1,\n family = poisson(\"log\"), # Poisson + log link\n data = dplyr::select(reg_df, -ctyu19nm))\npoisson_m1 %>% summary()\n\n\nCall:\nglm(formula = eq1, family = poisson(\"log\"), data = dplyr::select(reg_df, \n -ctyu19nm))\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) -1.027e+03 8.168e+00 -125.699 < 2e-16 ***\nlong -8.534e-01 9.080e-02 -9.399 < 2e-16 ***\nlat 1.456e+00 7.617e-02 19.115 < 2e-16 ***\ndate 5.153e-02 3.871e-04 133.121 < 2e-16 ***\nlt_illness 1.166e+01 7.701e-01 15.139 < 2e-16 ***\nB1 3.418e+00 8.005e-01 4.270 1.96e-05 ***\nB2 4.414e-01 3.249e-01 1.359 0.17421 \nB3 8.531e+00 5.480e-01 15.568 < 2e-16 ***\nB4 -7.129e-01 5.865e-01 -1.215 0.22418 \nB5 1.639e+00 5.048e-01 3.246 0.00117 ** \nB6 -1.282e+00 5.618e-01 -2.283 0.02245 * \nB7 -3.572e-01 8.411e-01 -0.425 0.67102 \nB8 -1.003e+00 6.262e-01 -1.602 0.10917 \nB9 1.655e+00 6.268e-01 2.640 0.00829 ** \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for poisson family taken to be 1)\n\n Null deviance: 51245 on 10649 degrees of freedom\nResidual deviance: 24458 on 10636 degrees of freedom\nAIC: 42364\n\nNumber of Fisher Scoring iterations: 5\n\n\nThe Poisson model seems to provide a more appropriate functional forms to identify the strength of the relationship between new COVID-19 cases and spatio-temporal dependencies as captured by a greater number (relative to the linear model) of basis functions coefficients becoming statistically significant. Yet, the Poisson model assumes that the mean and variance of the COVID-19 cases is the same. But, given the distribution of our dependent variable, its variance is likely to be greater than the mean. That means the data exhibit “overdispersion”. How do we know this? An estimate of the dispersion is given by the ratio of the deviance to the total degrees of freedom (the number of data points minus the number of covariates). In this case the dispersion estimate is:\n\npoisson_m1$deviance / poisson_m1$df.residual\n\n[1] 2.29953\n\n\nwhich is clearly greater than 1! i.e. the data are overdispersed.\nQuasipoisson Regression\nAn approach to account for overdispersion is to use quasipoisson when calling glm. The quasi-Poisson model assumes that the variance is proportional to the mean, and that the constant of the proportionality is the over-dispersion parameter. This model corrects the standard error of the estimated coefficients. So only p-values and t values are affected. No changes are recorded in terms of residual deviance (24458) and median of deviance residuals (-0.6993), compared to the standard Poisson model.\n\n# estimate a quasipoisson model\nqpoisson_m1 <- glm(eq1,\n family = quasipoisson(\"log\"), # QuasiPoisson + log link\n data = dplyr::select(reg_df, -ctyu19nm))\nqpoisson_m1 %>% summary()\n\n\nCall:\nglm(formula = eq1, family = quasipoisson(\"log\"), data = dplyr::select(reg_df, \n -ctyu19nm))\n\nCoefficients:\n Estimate Std. Error t value Pr(>|t|) \n(Intercept) -1.027e+03 1.215e+01 -84.490 < 2e-16 ***\nlong -8.534e-01 1.351e-01 -6.318 2.76e-10 ***\nlat 1.456e+00 1.133e-01 12.848 < 2e-16 ***\ndate 5.153e-02 5.759e-04 89.478 < 2e-16 ***\nlt_illness 1.166e+01 1.146e+00 10.176 < 2e-16 ***\nB1 3.418e+00 1.191e+00 2.870 0.00411 ** \nB2 4.414e-01 4.833e-01 0.913 0.36109 \nB3 8.531e+00 8.153e-01 10.464 < 2e-16 ***\nB4 -7.129e-01 8.726e-01 -0.817 0.41395 \nB5 1.639e+00 7.510e-01 2.182 0.02915 * \nB6 -1.282e+00 8.358e-01 -1.534 0.12499 \nB7 -3.572e-01 1.251e+00 -0.286 0.77526 \nB8 -1.003e+00 9.316e-01 -1.077 0.28162 \nB9 1.655e+00 9.325e-01 1.774 0.07603 . \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for quasipoisson family taken to be 2.213379)\n\n Null deviance: 51245 on 10649 degrees of freedom\nResidual deviance: 24458 on 10636 degrees of freedom\nAIC: NA\n\nNumber of Fisher Scoring iterations: 5\n\n\nNegative Binomial Regression\nAn alternative approach to deal with overdispersion is the Negative Binomial Model (NBM). This models relaxes the assumption of equality between the mean and variance. We estimate a NBM by using the function glm.nb from the MASS package.\n\n# estimate a negative binomial model\nnb_m1 <- glm.nb(eq1, \n data = dplyr::select(reg_df, -ctyu19nm))\nnb_m1 %>% summary()\n\n\nCall:\nglm.nb(formula = eq1, data = dplyr::select(reg_df, -ctyu19nm), \n init.theta = 1.493089258, link = log)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) -1.540e+03 1.596e+01 -96.459 < 2e-16 ***\nlong -8.402e-01 1.650e-01 -5.090 3.57e-07 ***\nlat 1.604e+00 1.456e-01 11.021 < 2e-16 ***\ndate 7.901e-02 7.522e-04 105.030 < 2e-16 ***\nlt_illness 1.440e+01 1.534e+00 9.387 < 2e-16 ***\nB1 5.121e+00 1.460e+00 3.508 0.000452 ***\nB2 1.622e-01 6.177e-01 0.263 0.792897 \nB3 9.502e+00 9.469e-01 10.035 < 2e-16 ***\nB4 -2.054e+00 1.183e+00 -1.736 0.082590 . \nB5 2.461e+00 9.802e-01 2.510 0.012059 * \nB6 -1.095e+00 1.089e+00 -1.005 0.314895 \nB7 6.486e-01 1.642e+00 0.395 0.692877 \nB8 -1.143e+00 1.225e+00 -0.933 0.350789 \nB9 1.068e+00 1.169e+00 0.914 0.360917 \n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for Negative Binomial(1.4931) family taken to be 1)\n\n Null deviance: 22092.1 on 10649 degrees of freedom\nResidual deviance: 8374.3 on 10636 degrees of freedom\nAIC: 34057\n\nNumber of Fisher Scoring iterations: 1\n\n Theta: 1.4931 \n Std. Err.: 0.0361 \n\n 2 x log-likelihood: -34027.2980 \n\n\nThe NBM leads to a significant reduction in residual deviance from 24,458 returned by the Poisson model to only 8,374. It points to a major improvement in explaining the spatio-temporal variability in the spread of COVID-19\nIncluding Interactions\nYet, we may further refine our model based on a different strategy. Let’s try running a NBM including interaction terms between spatial and temporal terms (i.e. longitude, latitude and date). We can do this by estimating the following model c_covid19_r ~ (long + lat + date)^2 + lt_illness + .\n\n# new model specification\neq2 <- n_covid19_r ~ (long + lat + date)^2 + lt_illness + .\n# estimate a negative binomial model\nnb_m2 <- glm.nb(eq2, \n data = dplyr::select(reg_df, -ctyu19nm))\nnb_m2 %>% summary()\n\n\nCall:\nglm.nb(formula = eq2, data = dplyr::select(reg_df, -ctyu19nm), \n init.theta = 1.56089592, link = log)\n\nCoefficients:\n Estimate Std. Error z value Pr(>|z|) \n(Intercept) 4.797e+03 6.955e+02 6.897 5.32e-12 ***\nlong -4.509e+01 1.559e+01 -2.892 0.00382 ** \nlat -1.185e+02 1.342e+01 -8.827 < 2e-16 ***\ndate -2.754e-01 3.788e-02 -7.270 3.61e-13 ***\nlt_illness 1.329e+01 1.522e+00 8.734 < 2e-16 ***\nB1 1.155e+01 1.571e+00 7.354 1.92e-13 ***\nB2 -4.181e-01 6.212e-01 -0.673 0.50095 \nB3 2.062e+01 1.408e+00 14.644 < 2e-16 ***\nB4 -6.669e+00 1.256e+00 -5.311 1.09e-07 ***\nB5 9.446e+00 1.170e+00 8.071 6.96e-16 ***\nB6 -1.050e+01 1.398e+00 -7.509 5.96e-14 ***\nB7 2.309e+01 2.626e+00 8.793 < 2e-16 ***\nB8 -5.111e+00 1.279e+00 -3.995 6.48e-05 ***\nB9 1.139e+00 1.159e+00 0.983 0.32575 \nlong:lat 1.574e+00 1.462e-01 10.763 < 2e-16 ***\nlong:date -1.937e-03 7.525e-04 -2.574 0.01005 * \nlat:date 6.713e-03 7.309e-04 9.185 < 2e-16 ***\n---\nSignif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1\n\n(Dispersion parameter for Negative Binomial(1.5609) family taken to be 1)\n\n Null deviance: 22557.0 on 10649 degrees of freedom\nResidual deviance: 8352.3 on 10633 degrees of freedom\nAIC: 33849\n\nNumber of Fisher Scoring iterations: 1\n\n Theta: 1.5609 \n Std. Err.: 0.0383 \n\n 2 x log-likelihood: -33813.3960 \n\n\nThis model leads to a slightly better model by returning a small reduction in the residual deviance and AIC score. Interestingly it also returns highly statistically significant coefficients for all three interaction terms between longitude and latitude (long:lat), longitude and date (long:date), and latitude and date (lat:date). The first indicates that as we move one degree north and west, the number of new cases tend to increase in 2 cases. The second indicates that UTLAs located further north of England tend to record a smaller number of cases over time. The third indicates that UTLAs on the west of England tend to report a much higher number of cases as time passes.\nYou can report the output for all models estimated above by executing (after removing #):\n\n# export_summs(lm_m, poisson_m, qpoisson_m1, nb_m1, nb_m2)\n\nNote that you may need to install the huxtable package.\n\n10.6.2.1 Model Comparision\nTo compare regression models based on different specifications and assumptions, like those reported above, you may want to consider the cross-validation approach used in Chapter 5. An alternative approach if you would like to get a quick sense of model fit is to explore the correlation between observed and predicted values of the dependent variable. For our models, we can achieve this by executing:\n\n# computing predictions for all models\nlm_cnt <- predict(lm_m)\npoisson_cnt <- predict(poisson_m1)\nnb1_cnt <- predict(nb_m1)\nnb2_cnt <- predict(nb_m2)\nreg_df <- cbind(reg_df, lm_cnt, poisson_cnt, nb1_cnt, nb2_cnt)\n\n# computing correlation coefficients\ncormat <- cor(reg_df[, c(\"n_covid19_r\", \"lm_cnt\", \"poisson_cnt\", \"nb1_cnt\", \"nb2_cnt\")], \n use=\"complete.obs\", \n method=\"pearson\")\n\n# significance test\nsig1 <- corrplot::cor.mtest(reg_df[, c(\"n_covid19_r\", \"lm_cnt\", \"poisson_cnt\", \"nb1_cnt\", \"nb2_cnt\")],\n conf.level = .95)\n\n# create a correlogram\ncorrplot::corrplot.mixed(cormat,\n number.cex = 1,\n tl.pos = \"d\",\n tl.cex = 0.9)\n\n\n\n\n\n\n\nAll the models do a relatively job at predicting the observed count of new COVID-19 cases. They display correlation coefficients of 0.64/5 and high degree of correlation between them. These models will provide a good starting point for the assignment. There are a potentially few easy ways to make some considerable improvement.\n\n\nOption 1 Remove all zeros from the dependent variable n_covid19_r. They are likely to be affecting the ability of the model to predict positive values which are of main interest if we want to understand the spatio-temporal patterns of the outbreak.\n\nOption 2 Remove all zeros from the dependent variable and additionally use its log for the regression model.\n\nOption 3 Include more explanatory variables. Look for factors which can explain the spatial-temporal variability of the current COVID-19 outbreak. Look for hypotheses / anecdotal evidence from the newspapers and social media.\n\nOption 4 Check for collinearity. Collinearity is likely to be an issue given the way basis functions are created. Checking for collinearity of course will not improve the fit of the existing model but it is important to remove collinear terms if statistical inference is a key goal - which in this case is. Over to you now!", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] }, { "objectID": "10-st_analysis.html#questions", "href": "10-st_analysis.html#questions", "title": "10  Spatio-Temporal Analysis", - "section": "10.7 Questions", - "text": "10.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nCreate a spatio-temporal visualisation.\nFit a ST model to assess changes over space and time.\n\nAnalyse and discuss:\n\nDo the results for your GWR and ST results differ: How do regression coefficients vary across space? Is this variation statistically significant?\nDoes the ST model indicate significant variations over time? How?\n\n\n\n\n\nCressie, Noel, and Gardar Johannesson. 2008. “Fixed Rank Kriging for Very Large Spatial Data Sets.” Journal of the Royal Statistical Society: Series B (Statistical Methodology) 70 (1): 209–26.\n\n\nGabadinho, Alexis, Gilbert Ritschard, Matthias Studer, and Nicolas S Müller. 2009. “Mining Sequence Data in r with the TraMineR Package: A User’s Guide.” Geneva: Department of Econometrics and Laboratory of Demography, University of Geneva.\n\n\nHyndman, Rob J, and George Athanasopoulos. 2018. Forecasting: Principles and Practice. OTexts.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\nPebesma, Edzer et al. 2012. “Spacetime: Spatio-Temporal Data in r.” Journal of Statistical Software 51 (7): 1–30.\n\n\nWikle, Christopher K, Andrew Zammit-Mangion, and Noel Cressie. 2019. Spatio-Temporal Statistics with r. CRC Press.\n\n\nZammit-Mangion, Andrew, and Noel Cressie. 2017. “FRK: An r Package for Spatial and Spatio-Temporal Prediction with Large Datasets.” arXiv Preprint arXiv:1705.08105." + "section": "\n10.7 Questions", + "text": "10.7 Questions\nWe will continue to use the COVID-19 dataset. Please see Chapter 11 for details on the data.\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nUsing these data, you are required to address the following challenges:\n\nCreate a spatio-temporal visualisation.\nFit a ST model to assess changes over space and time.\n\nAnalyse and discuss:\n\nDo the results for your GWR and ST results differ: How do regression coefficients vary across space? Is this variation statistically significant?\nDoes the ST model indicate significant variations over time? How?\n\n\n\n\n\n\n\nCressie, Noel, and Gardar Johannesson. 2008. “Fixed Rank Kriging for Very Large Spatial Data Sets.” Journal of the Royal Statistical Society: Series B (Statistical Methodology) 70 (1): 209–26.\n\n\nGabadinho, Alexis, Gilbert Ritschard, Matthias Studer, and Nicolas S Müller. 2009. “Mining Sequence Data in r with the TraMineR Package: A User’s Guide.” Geneva: Department of Econometrics and Laboratory of Demography, University of Geneva.\n\n\nHyndman, Rob J, and George Athanasopoulos. 2018. Forecasting: Principles and Practice. OTexts.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019. Geocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\nPebesma, Edzer et al. 2012. “Spacetime: Spatio-Temporal Data in r.” Journal of Statistical Software 51 (7): 1–30.\n\n\nWikle, Christopher K, Andrew Zammit-Mangion, and Noel Cressie. 2019. Spatio-Temporal Statistics with r. CRC Press.\n\n\nZammit-Mangion, Andrew, and Noel Cressie. 2017. “FRK: An r Package for Spatial and Spatio-Temporal Prediction with Large Datasets.” arXiv Preprint arXiv:1705.08105.", + "crumbs": [ + "10  Spatio-Temporal Analysis" + ] + }, + { + "objectID": "11-datasets.html", + "href": "11-datasets.html", + "title": "11  Data Sets", + "section": "", + "text": "11.1 Madrid AirBnb\nThis dataset contains a pre-processed set of properties advertised on the AirBnb website within the region of Madrid (Spain), together with house characteristics.", + "crumbs": [ + "11  Data Sets" + ] }, { "objectID": "11-datasets.html#madrid-airbnb", "href": "11-datasets.html#madrid-airbnb", "title": "11  Data Sets", - "section": "11.1 Madrid AirBnb", - "text": "11.1 Madrid AirBnb\nThis dataset contains a pre-processed set of properties advertised on the AirBnb website within the region of Madrid (Spain), together with house characteristics.\n\nAvailability\nThe dataset is stored on a Geopackage that can be found, within the structure of this project, under:\n\npath <- \"data/assignment_1_madrid/madrid_abb.gpkg\"\n\n\ndb <- st_read(path)\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\n\n\nVariables\nFor each of the 17 properties, the following characteristics are available:\n\nprice: [string] Price with currency\nprice_usd: [int] Price expressed in USD\nlog1pm_price_usd: [float] Log of the price\naccommodates: [integer] Number of people the property accommodates\nbathrooms: [integer] Number of bathrooms\nbedrooms: [integer] Number of bedrooms\nbeds: [integer] Number of beds\nneighbourhood: [string] Name of the neighbourhood the property is located in\nroom_type: [string] Type of room offered (shared, private, entire home, hotel room)\nproperty_type: [string] Type of property advertised (apartment, house, hut, etc.)\nWiFi: [binary] Takes 1 if the property has WiFi, 0 otherwise\nCoffee: [binary] Takes 1 if the property has a coffee maker, 0 otherwise\nGym: [binary] Takes 1 if the property has access to a gym, 0 otherwise\nParking: [binary] Takes 1 if the property offers parking, 0 otherwise\nkm_to_retiro: [float] Euclidean distance from the property to the El Retiro park\ngeom: [geometry] Point geometry\n\n\n\nProjection\nThe location of each property is stored as point geometries and expressed in longitude and latitude coordinates:\n\nst_crs(db)\n\nCoordinate Reference System:\n User input: WGS 84 \n wkt:\nGEOGCRS[\"WGS 84\",\n ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n MEMBER[\"World Geodetic System 1984 (Transit)\"],\n MEMBER[\"World Geodetic System 1984 (G730)\"],\n MEMBER[\"World Geodetic System 1984 (G873)\"],\n MEMBER[\"World Geodetic System 1984 (G1150)\"],\n MEMBER[\"World Geodetic System 1984 (G1674)\"],\n MEMBER[\"World Geodetic System 1984 (G1762)\"],\n MEMBER[\"World Geodetic System 1984 (G2139)\"],\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]],\n ENSEMBLEACCURACY[2.0]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n CS[ellipsoidal,2],\n AXIS[\"geodetic latitude (Lat)\",north,\n ORDER[1],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n AXIS[\"geodetic longitude (Lon)\",east,\n ORDER[2],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n USAGE[\n SCOPE[\"Horizontal component of 3D system.\"],\n AREA[\"World.\"],\n BBOX[-90,-180,90,180]],\n ID[\"EPSG\",4326]]\n\n\n\n\nSource & Pre-processing\nThe data are sourced from Inside Airbnb. A Jupyter notebook in Python (available at data/assignment_1_madrid/clean_data.ipynb) details the process from the original file available from source to the data in madrid_abb.gpkg." + "section": "", + "text": "Availability\nThe dataset is stored on a Geopackage that can be found, within the structure of this project, under:\n\npath <- \"data/assignment_1_madrid/madrid_abb.gpkg\"\n\n\ndb <- st_read(path)\n\nReading layer `madrid_abb' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_1_madrid/madrid_abb.gpkg' \n using driver `GPKG'\nSimple feature collection with 18399 features and 16 fields\nGeometry type: POINT\nDimension: XY\nBounding box: xmin: -3.86391 ymin: 40.33243 xmax: -3.556 ymax: 40.56274\nGeodetic CRS: WGS 84\n\n\nVariables\nFor each of the 17 properties, the following characteristics are available:\n\n\nprice: [string] Price with currency\n\nprice_usd: [int] Price expressed in USD\n\nlog1pm_price_usd: [float] Log of the price\n\naccommodates: [integer] Number of people the property accommodates\n\nbathrooms: [integer] Number of bathrooms\n\nbedrooms: [integer] Number of bedrooms\n\nbeds: [integer] Number of beds\n\nneighbourhood: [string] Name of the neighbourhood the property is located in\n\nroom_type: [string] Type of room offered (shared, private, entire home, hotel room)\n\nproperty_type: [string] Type of property advertised (apartment, house, hut, etc.)\n\nWiFi: [binary] Takes 1 if the property has WiFi, 0 otherwise\n\nCoffee: [binary] Takes 1 if the property has a coffee maker, 0 otherwise\n\nGym: [binary] Takes 1 if the property has access to a gym, 0 otherwise\n\nParking: [binary] Takes 1 if the property offers parking, 0 otherwise\n\nkm_to_retiro: [float] Euclidean distance from the property to the El Retiro park\n\ngeom: [geometry] Point geometry\nProjection\nThe location of each property is stored as point geometries and expressed in longitude and latitude coordinates:\n\nst_crs(db)\n\nCoordinate Reference System:\n User input: WGS 84 \n wkt:\nGEOGCRS[\"WGS 84\",\n ENSEMBLE[\"World Geodetic System 1984 ensemble\",\n MEMBER[\"World Geodetic System 1984 (Transit)\"],\n MEMBER[\"World Geodetic System 1984 (G730)\"],\n MEMBER[\"World Geodetic System 1984 (G873)\"],\n MEMBER[\"World Geodetic System 1984 (G1150)\"],\n MEMBER[\"World Geodetic System 1984 (G1674)\"],\n MEMBER[\"World Geodetic System 1984 (G1762)\"],\n MEMBER[\"World Geodetic System 1984 (G2139)\"],\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]],\n ENSEMBLEACCURACY[2.0]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n CS[ellipsoidal,2],\n AXIS[\"geodetic latitude (Lat)\",north,\n ORDER[1],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n AXIS[\"geodetic longitude (Lon)\",east,\n ORDER[2],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n USAGE[\n SCOPE[\"Horizontal component of 3D system.\"],\n AREA[\"World.\"],\n BBOX[-90,-180,90,180]],\n ID[\"EPSG\",4326]]\n\n\nSource & Pre-processing\nThe data are sourced from Inside Airbnb. A Jupyter notebook in Python (available at data/assignment_1_madrid/clean_data.ipynb) details the process from the original file available from source to the data in madrid_abb.gpkg.", + "crumbs": [ + "11  Data Sets" + ] }, { "objectID": "11-datasets.html#england-covid-19", "href": "11-datasets.html#england-covid-19", "title": "11  Data Sets", - "section": "11.2 England COVID-19", - "text": "11.2 England COVID-19\nThis dataset contains:\n\ndaily COVID-19 confirmed cases from 1st January, 2020 to 2nd February, 2021 from the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government.\n\nThe data are at the Upper Tier Local Authority District (UTLAD) level - also known as Counties and Unitary Authorities.\n\nAvailability\nThe dataset is stored on a Geopackage:\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202223/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\n\n\nVariables\nThe data set contains 508 variables:\n\nobjectid: [integer] unit identifier\nctyua19cd: [integer] Upper Tier Local Authority District (or Counties and Unitary Authorities) identifier\nctyua19nm: [character] Upper Tier Local Authority District (or Counties and Unitary Authorities) name\nRegion: [character] Region name\nlong: [numeric] longitude\nlat: [numeric] latitude\nst_areasha: [numeric] area in hectare\nX2020.01.31 to X2021.02.05: [numeric] Daily COVID-19 cases from 31st January, 2020 to 5th February, 2021\nIMD...Average.score - IMD.2019...Local.concentration: [numeric] IMD indicators - for details see File 11: upper-tier local authority summaries.\nResidents: [numeric] Total resident population\nHouseholds: [numeric] Total households\nDwellings: [numeric] Total dwellings\nHousehold_Spaces: [numeric] Total household spaces\nAged_16plus to Other_industry: [numeric] comprise 114 variables relating to various population and household attributes of the resident population. A description of all these variables can be found here\ngeom: [geometry] Point geometry\n\n\n\nProjection\nDetails of the coordinate reference system:\n\nst_crs(sdf)\n\nCoordinate Reference System:\n User input: OSGB36 / British National Grid \n wkt:\nPROJCRS[\"OSGB36 / British National Grid\",\n BASEGEOGCRS[\"OSGB36\",\n DATUM[\"Ordnance Survey of Great Britain 1936\",\n ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,\n LENGTHUNIT[\"metre\",1]]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4277]],\n CONVERSION[\"British National Grid\",\n METHOD[\"Transverse Mercator\",\n ID[\"EPSG\",9807]],\n PARAMETER[\"Latitude of natural origin\",49,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8801]],\n PARAMETER[\"Longitude of natural origin\",-2,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8802]],\n PARAMETER[\"Scale factor at natural origin\",0.9996012717,\n SCALEUNIT[\"unity\",1],\n ID[\"EPSG\",8805]],\n PARAMETER[\"False easting\",400000,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8806]],\n PARAMETER[\"False northing\",-100000,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8807]]],\n CS[Cartesian,2],\n AXIS[\"(E)\",east,\n ORDER[1],\n LENGTHUNIT[\"metre\",1]],\n AXIS[\"(N)\",north,\n ORDER[2],\n LENGTHUNIT[\"metre\",1]],\n USAGE[\n SCOPE[\"Engineering survey, topographic mapping.\"],\n AREA[\"United Kingdom (UK) - offshore to boundary of UKCS within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain (England, Wales and Scotland). Isle of Man onshore.\"],\n BBOX[49.75,-9,61.01,2.01]],\n ID[\"EPSG\",27700]]" + "section": "\n11.2 England COVID-19", + "text": "11.2 England COVID-19\nThis dataset contains:\n\ndaily COVID-19 confirmed cases from 1st January, 2020 to 2nd February, 2021 from the GOV.UK dashboard;\nresident population characteristics from the 2011 census, available from the Office of National Statistics; and,\n2019 Index of Multiple Deprivation (IMD) data from GOV.UK and published by the Ministry of Housing, Communities & Local Government.\n\nThe data are at the Upper Tier Local Authority District (UTLAD) level - also known as Counties and Unitary Authorities.\nAvailability\nThe dataset is stored on a Geopackage:\n\nsdf <- st_read(\"data/assignment_2_covid/covid19_eng.gpkg\")\n\nReading layer `covid19_eng' from data source \n `/Users/franciscorowe/Dropbox/Francisco/uol/teaching/envs453/202324/san/data/assignment_2_covid/covid19_eng.gpkg' \n using driver `GPKG'\nSimple feature collection with 149 features and 507 fields\nGeometry type: MULTIPOLYGON\nDimension: XY\nBounding box: xmin: 134112.4 ymin: 11429.67 xmax: 655653.8 ymax: 657536\nProjected CRS: OSGB36 / British National Grid\n\n\nVariables\nThe data set contains 508 variables:\n\n\nobjectid: [integer] unit identifier\n\nctyua19cd: [integer] Upper Tier Local Authority District (or Counties and Unitary Authorities) identifier\n\nctyua19nm: [character] Upper Tier Local Authority District (or Counties and Unitary Authorities) name\n\nRegion: [character] Region name\n\nlong: [numeric] longitude\n\nlat: [numeric] latitude\n\nst_areasha: [numeric] area in hectare\n\nX2020.01.31 to X2021.02.05: [numeric] Daily COVID-19 cases from 31st January, 2020 to 5th February, 2021\n\nIMD...Average.score - IMD.2019...Local.concentration: [numeric] IMD indicators - for details see File 11: upper-tier local authority summaries.\n\nResidents: [numeric] Total resident population\n\nHouseholds: [numeric] Total households\n\nDwellings: [numeric] Total dwellings\n\nHousehold_Spaces: [numeric] Total household spaces\n\nAged_16plus to Other_industry: [numeric] comprise 114 variables relating to various population and household attributes of the resident population. A description of all these variables can be found here\n\n\ngeom: [geometry] Point geometry\nProjection\nDetails of the coordinate reference system:\n\nst_crs(sdf)\n\nCoordinate Reference System:\n User input: OSGB36 / British National Grid \n wkt:\nPROJCRS[\"OSGB36 / British National Grid\",\n BASEGEOGCRS[\"OSGB36\",\n DATUM[\"Ordnance Survey of Great Britain 1936\",\n ELLIPSOID[\"Airy 1830\",6377563.396,299.3249646,\n LENGTHUNIT[\"metre\",1]]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4277]],\n CONVERSION[\"British National Grid\",\n METHOD[\"Transverse Mercator\",\n ID[\"EPSG\",9807]],\n PARAMETER[\"Latitude of natural origin\",49,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8801]],\n PARAMETER[\"Longitude of natural origin\",-2,\n ANGLEUNIT[\"degree\",0.0174532925199433],\n ID[\"EPSG\",8802]],\n PARAMETER[\"Scale factor at natural origin\",0.9996012717,\n SCALEUNIT[\"unity\",1],\n ID[\"EPSG\",8805]],\n PARAMETER[\"False easting\",400000,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8806]],\n PARAMETER[\"False northing\",-100000,\n LENGTHUNIT[\"metre\",1],\n ID[\"EPSG\",8807]]],\n CS[Cartesian,2],\n AXIS[\"(E)\",east,\n ORDER[1],\n LENGTHUNIT[\"metre\",1]],\n AXIS[\"(N)\",north,\n ORDER[2],\n LENGTHUNIT[\"metre\",1]],\n USAGE[\n SCOPE[\"Engineering survey, topographic mapping.\"],\n AREA[\"United Kingdom (UK) - offshore to boundary of UKCS within 49°45'N to 61°N and 9°W to 2°E; onshore Great Britain (England, Wales and Scotland). Isle of Man onshore.\"],\n BBOX[49.75,-9,61.01,2.01]],\n ID[\"EPSG\",27700]]", + "crumbs": [ + "11  Data Sets" + ] }, { "objectID": "references.html", "href": "references.html", "title": "References", "section": "", - "text": "Anselin, Luc. 1988. Spatial Econometrics: Methods and Models.\nVol. 4. Springer Science & Business Media.\n\n\n———. 2003. “Spatial Externalities, Spatial Multipliers, and\nSpatial Econometrics.” International Regional Science\nReview 26 (2): 153–66.\n\n\n———. 2007. “Spatial Regression Analysis in r–a Workbook.”\nCenter for Spatially Integrated Social Science. http://csiss.org/GISPopSci/workshops/2011/PSU/readings/W15_Anselin2007.pdf.\n\n\nAnselin, Luc, and Sergio J. Rey. 2014. Modern Spatial Econometrics\nin Practice: A Guide to GeoDa, GeoDaSpace and PySAL. GeoDa Press\nLLC.\n\n\nArribas-Bel, Dani. 2014. “Spatial Data, Analysis, and Regression-a\nMini Course.” REGION 1 (1): R1. http://darribas.org/sdar_mini.\n\n\n———. 2019. “A Course on Geographic Data Science.” The\nJournal of Open Source Education 2 (14). https://doi.org/https://doi.org/10.21105/jose.00042.\n\n\nArribas-Bel, Daniel, M.-À. Garcia-López, and Elisabet Viladecans-Marsal.\n2021. “Building(s and) Cities: Delineating Urban Areas with a\nMachine Learning Algorithm.” Journal of Urban Economics\n125 (September): 103217. https://doi.org/10.1016/j.jue.2019.103217.\n\n\nBanerjee, Sudipto, Bradley P Carlin, and Alan E Gelfand. 2014.\nHierarchical Modeling and Analysis for Spatial Data. Crc Press.\n\n\nBelsley, David A, Edwin Kuh, and Roy E Welsch. 2005. Regression\nDiagnostics: Identifying Influential Data and Sources of\nCollinearity. Vol. 571. John Wiley & Sons.\n\n\nBivand, Roger S., Edzer Pebesma, and Virgilio Gómez-Rubio. 2013.\nApplied Spatial Data Analysis with r. Springer New York. https://doi.org/10.1007/978-1-4614-7618-4.\n\n\nBrunsdon, Chris, and Lex Comber. 2015. An Introduction to r for\nSpatial Analysis & Mapping. Sage.\n\n\nBrunsdon, Chris, Stewart Fotheringham, and Martin Charlton. 1998.\n“Geographically Weighted Regression.” Journal of the\nRoyal Statistical Society: Series D (The Statistician) 47 (3):\n431–43.\n\n\nCasado-Díaz, José Manuel, Lucas Martínez-Bernabéu, and Francisco Rowe.\n2017. “An Evolutionary Approach to the Delimitation of Labour\nMarket Areas: An Empirical Application for Chile.” Spatial\nEconomic Analysis 12 (4): 379–403. https://doi.org/10.1080/17421772.2017.1273541.\n\n\nComber, Alexis, Christopher Brunsdon, Martin Charlton, Guanpeng Dong,\nRichard Harris, Binbin Lu, Yihe Lü, et al. 2022. “A Route Map for\nSuccessful Applications of Geographically Weighted Regression.”\nGeographical Analysis 55 (1): 155–78. https://doi.org/10.1111/gean.12316.\n\n\nCressie, Noel. 2015. Statistics for Spatial Data. John Wiley\n& Sons.\n\n\nCressie, Noel, and Gardar Johannesson. 2008. “Fixed Rank Kriging\nfor Very Large Spatial Data Sets.” Journal of the Royal\nStatistical Society: Series B (Statistical Methodology) 70 (1):\n209–26.\n\n\nFotheringham, A Stewart, and Morton E O’Kelly. 1989. Spatial\nInteraction Models: Formulations and Applications. Vol. 1. Kluwer\nAcademic Publishers Dordrecht.\n\n\nFotheringham, A Stewart, and David WS Wong. 1991. “The Modifiable\nAreal Unit Problem in Multivariate Statistical Analysis.”\nEnvironment and Planning A 23 (7): 1025–44.\n\n\nFotheringham, Stewart, Chris Brunsdon, and Martin Charlton. 2002.\nGeographically Weighted Regression. John Wiley & Sons.\n\n\nGabadinho, Alexis, Gilbert Ritschard, Matthias Studer, and Nicolas S\nMüller. 2009. “Mining Sequence Data in r with the TraMineR\nPackage: A User’s Guide.” Geneva: Department of Econometrics\nand Laboratory of Demography, University of Geneva.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using\nRegression and Multilevel/Hierarchical Models. Cambridge University\nPress.\n\n\nGibbons, Stephen, Henry G Overman, and Eleonora Patacchini. 2014.\n“Spatial Methods.”\n\n\nGrolemund, Garrett, and Hadley Wickham. 2019. R for Data\nScience. O’Reilly, US. https://r4ds.had.co.nz.\n\n\nHyndman, Rob J, and George Athanasopoulos. 2018. Forecasting:\nPrinciples and Practice. OTexts.\n\n\nKong, Xiangjie, Menglin Li, Kai Ma, Kaiqi Tian, Mengyuan Wang, Zhaolong\nNing, and Feng Xia. 2018. “Big Trajectory Data: A Survey of\nApplications and Services.” IEEE Access 6: 58295–306. https://doi.org/10.1109/access.2018.2873779.\n\n\nKwan, Mei-Po, and Jiyeong Lee. 2004. “Geovisualization of Human\nActivity Patterns Using 3D GIS: A Time-Geographic Approach.”\nSpatially Integrated Social Science 27: 721–44.\n\n\nLoidl, Martin, Gudrun Wallentin, Robin Wendel, and Bernhard Zagel. 2016.\n“Mapping Bicycle Crash Risk Patterns on the Local Scale.”\nSafety 2 (3): 17. https://doi.org/10.3390/safety2030017.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019.\nGeocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\nLu, Binbin, Paul Harris, Martin Charlton, and Chris Brunsdon. 2014.\n“The GWmodel r Package: Further Topics for Exploring Spatial\nHeterogeneity Using Geographically Weighted Models.”\nGeo-Spatial Information Science 17 (2): 85–101.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel\nModelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04.\n\n\nNiedomysl, Thomas. 2011. “How Migration Motives Change over\nMigration Distance: Evidence on Variation Across Socio-Economic and\nDemographic Groups.” Regional Studies 45 (6): 843–55. https://doi.org/10.1080/00343401003614266.\n\n\nÖnnerfors, Åsa, Mariana Kotzeva, Teodóra Brandmüller, et al. 2019.\n“Eurostat Regional Yearbook 2019 Edition.”\n\n\nOpenshaw, Stan. 1981. “The Modifiable Areal Unit Problem.”\nQuantitative Geography: A British View, 60–69.\n\n\nPatias, Nikos, Francisco Rowe, and Dani Arribas-Bel. 2021.\n“Trajectories of Neighbourhood Inequality in Britain: Unpacking\nInter-Regional Socioeconomic Imbalances,\n1971-2011.” The Geographical Journal 188\n(2): 150–65. https://doi.org/10.1111/geoj.12420.\n\n\nPatias, Nikos, Francisco Rowe, and Stefano Cavazzi. 2019. “A\nScalable Analytical Framework for Spatio-Temporal Analysis of\nNeighborhood Change: A Sequence Analysis Approach.” In, 223–41.\nSpringer International Publishing. https://doi.org/10.1007/978-3-030-14745-7_13.\n\n\nPebesma, Edzer et al. 2012. “Spacetime: Spatio-Temporal Data in\nr.” Journal of Statistical Software 51 (7): 1–30.\n\n\nRey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. forthcoming.\nGeographic Data Science with PySAL and the PyData Stack. CRC\npress.\n\n\nRobinson, WS. 1950. “Ecological Correlations and Individual\nBehavior.” American Sociological Review 15 (195):\n351–57.\n\n\nRowe, Francisco. 2022. “Introduction to Geographic Data\nScience.” Open Science Framework, August. https://doi.org/10.17605/OSF.IO/VHY2P.\n\n\nRowe, Francisco, Robin Lovelace, and Adam Dennett. 2022. “Spatial\nInteraction Modelling: A Manifesto.” http://dx.doi.org/10.31219/osf.io/xcdms.\n\n\nRowe, Francisco, and Nikos Patias. 2020. “Mapping the Spatial\nPatterns of Internal Migration in Europe.” Regional Studies,\nRegional Science 7 (1): 390–93. https://doi.org/10.1080/21681376.2020.1811139.\n\n\nSingleton, Alex. 2017. “Geographic Data Science for Urban\nAnalytics.” http://www.alex-singleton.com/GDS_UA_2017/.\n\n\nSingleton, Alexander D., and Seth E. Spielman. 2013. “The Past,\nPresent, and Future of Geodemographic Research in the United States and\nUnited Kingdom.” The Professional Geographer 66 (4):\n558–67. https://doi.org/10.1080/00330124.2013.848764.\n\n\nStillwell, John, Konstantinos Daras, and Martin Bell. 2018.\n“Spatial Aggregation Methods for Investigating the MAUP Effects in\nMigration Analysis.” Applied Spatial Analysis and Policy\n11 (4): 693–711. https://doi.org/10.1007/s12061-018-9274-6.\n\n\nTao, Sui, Jonathan Corcoran, Francisco Rowe, and Mark Hickman. 2018.\n“To Travel or Not to Travel: ‘Weather’\nIs the Question. Modelling the Effect of Local Weather Conditions on Bus\nRidership.” Transportation Research Part C: Emerging\nTechnologies 86 (January): 147–67. https://doi.org/10.1016/j.trc.2017.11.005.\n\n\nWheeler, David, and Michael Tiefelsdorf. 2005. “Multicollinearity\nand Correlation Among Local Regression Coefficients in Geographically\nWeighted Regression.” Journal of Geographical Systems 7\n(2): 161–87.\n\n\nWikle, Christopher K, Andrew Zammit-Mangion, and Noel Cressie. 2019.\nSpatio-Temporal Statistics with r. CRC Press.\n\n\nWilliamson, Paul. 2018. “Survey Analysis.”\n\n\nWolf, Levi John, Sean Fox, Rich Harris, Ron Johnston, Kelvyn Jones,\nDavid Manley, Emmanouil Tranos, and Wenfei Winnie Wang. 2020.\n“Quantitative Geography III: Future Challenges and Challenging\nFutures.” Progress in Human Geography 45 (3): 596–608.\nhttps://doi.org/10.1177/0309132520924722.\n\n\nXie, Yihui, JJ Allaire, and Garrett Grolemund. 2019. R Markdown: The\nDefinitive Guide. CRC Press, Taylor & Francis, Chapman &\nHall Book. https://bookdown.org/yihui/rmarkdown/.\n\n\nZammit-Mangion, Andrew, and Noel Cressie. 2017. “FRK: An r Package\nfor Spatial and Spatio-Temporal Prediction with Large Datasets.”\narXiv Preprint arXiv:1705.08105." + "text": "Anselin, Luc. 1988. Spatial Econometrics: Methods and Models.\nVol. 4. Springer Science & Business Media.\n\n\n———. 2003. “Spatial Externalities, Spatial Multipliers, and\nSpatial Econometrics.” International Regional Science\nReview 26 (2): 153–66.\n\n\n———. 2007. “Spatial Regression Analysis in r–a Workbook.”\nCenter for Spatially Integrated Social Science. http://csiss.org/GISPopSci/workshops/2011/PSU/readings/W15_Anselin2007.pdf.\n\n\nAnselin, Luc, and Sergio J. Rey. 2014. Modern Spatial Econometrics\nin Practice: A Guide to GeoDa, GeoDaSpace and PySAL. GeoDa Press\nLLC.\n\n\nAppelhans, Tim, Florian Detsch, Christoph Reudenbach, and Stefan\nWoellauer. 2022. Mapview: Interactive Viewing of Spatial Data in\nr. https://github.com/r-spatial/mapview.\n\n\nArribas-Bel, Dani. 2014. “Spatial Data, Analysis, and Regression-a\nMini Course.” REGION 1 (1): R1. http://darribas.org/sdar_mini.\n\n\n———. 2019. “A Course on Geographic Data Science.” The\nJournal of Open Source Education 2 (14). https://doi.org/https://doi.org/10.21105/jose.00042.\n\n\nArribas-Bel, Daniel, M.-À. Garcia-López, and Elisabet Viladecans-Marsal.\n2021. “Building(s and) Cities: Delineating Urban Areas with a\nMachine Learning Algorithm.” Journal of Urban Economics\n125 (September): 103217. https://doi.org/10.1016/j.jue.2019.103217.\n\n\nBaddeley, Adrian, Ege Rubak, and Rolf Turner. 2015. Spatial Point\nPatterns: Methodology and Applications with r. CRC press.\n\n\nBaddeley, Adrian, Rolf Turner, and Ege Rubak. 2022. Spatstat:\nSpatial Point Pattern Analysis, Model-Fitting, Simulation, Tests.\nhttp://spatstat.org/.\n\n\nBanerjee, Sudipto, Bradley P Carlin, and Alan E Gelfand. 2014.\nHierarchical Modeling and Analysis for Spatial Data. Crc Press.\n\n\nBelsley, David A, Edwin Kuh, and Roy E Welsch. 2005. Regression\nDiagnostics: Identifying Influential Data and Sources of\nCollinearity. Vol. 571. John Wiley & Sons.\n\n\nBivand, Roger. 2022. Spdep: Spatial Dependence: Weighting Schemes,\nStatistics.\n\n\nBivand, Roger S., Edzer Pebesma, and Virgilio Gómez-Rubio. 2013.\nApplied Spatial Data Analysis with r. Springer New York. https://doi.org/10.1007/978-1-4614-7618-4.\n\n\nBivand, Roger, and Gianfranco Piras. 2022. Spatialreg: Spatial\nRegression Analysis. https://CRAN.R-project.org/package=spatialreg.\n\n\nBrunsdon, Chris, and Lex Comber. 2015. An Introduction to r for\nSpatial Analysis & Mapping. Sage.\n\n\nBrunsdon, Chris, Stewart Fotheringham, and Martin Charlton. 1998.\n“Geographically Weighted Regression.” Journal of the\nRoyal Statistical Society: Series D (The Statistician) 47 (3):\n431–43.\n\n\nCasado-Díaz, José Manuel, Lucas Martínez-Bernabéu, and Francisco Rowe.\n2017. “An Evolutionary Approach to the Delimitation of Labour\nMarket Areas: An Empirical Application for Chile.” Spatial\nEconomic Analysis 12 (4): 379–403. https://doi.org/10.1080/17421772.2017.1273541.\n\n\nComber, Alexis, Christopher Brunsdon, Martin Charlton, Guanpeng Dong,\nRichard Harris, Binbin Lu, Yihe Lü, et al. 2022. “A Route Map for\nSuccessful Applications of Geographically Weighted Regression.”\nGeographical Analysis 55 (1): 155–78. https://doi.org/10.1111/gean.12316.\n\n\nCressie, Noel. 2015. Statistics for Spatial Data. John Wiley\n& Sons.\n\n\nCressie, Noel, and Gardar Johannesson. 2008. “Fixed Rank Kriging\nfor Very Large Spatial Data Sets.” Journal of the Royal\nStatistical Society: Series B (Statistical Methodology) 70 (1):\n209–26.\n\n\nDunnington, Dewey, Edzer Pebesma, and Ege Rubak. 2023. S2: Spherical\nGeometry Operators Using the S2 Geometry Library. https://CRAN.R-project.org/package=s2.\n\n\nFotheringham, A Stewart, and Morton E O’Kelly. 1989. Spatial\nInteraction Models: Formulations and Applications. Vol. 1. Kluwer\nAcademic Publishers Dordrecht.\n\n\nFotheringham, A Stewart, and David WS Wong. 1991. “The Modifiable\nAreal Unit Problem in Multivariate Statistical Analysis.”\nEnvironment and Planning A 23 (7): 1025–44.\n\n\nFotheringham, Stewart, Chris Brunsdon, and Martin Charlton. 2002.\nGeographically Weighted Regression. John Wiley & Sons.\n\n\nGabadinho, Alexis, Gilbert Ritschard, Matthias Studer, and Nicolas S\nMüller. 2009. “Mining Sequence Data in r with the TraMineR\nPackage: A User’s Guide.” Geneva: Department of Econometrics\nand Laboratory of Demography, University of Geneva.\n\n\nGelman, Andrew, and Jennifer Hill. 2006. Data Analysis Using\nRegression and Multilevel/Hierarchical Models. Cambridge University\nPress.\n\n\nGibbons, Stephen, Henry G Overman, and Eleonora Patacchini. 2014.\n“Spatial Methods.”\n\n\nGrolemund, Garrett, and Hadley Wickham. 2019. R for Data\nScience. O’Reilly, US. https://r4ds.had.co.nz.\n\n\nHyndman, Rob J, and George Athanasopoulos. 2018. Forecasting:\nPrinciples and Practice. OTexts.\n\n\nKong, Xiangjie, Menglin Li, Kai Ma, Kaiqi Tian, Mengyuan Wang, Zhaolong\nNing, and Feng Xia. 2018. “Big Trajectory Data: A Survey of\nApplications and Services.” IEEE Access 6: 58295–306. https://doi.org/10.1109/access.2018.2873779.\n\n\nKwan, Mei-Po, and Jiyeong Lee. 2004. “Geovisualization of Human\nActivity Patterns Using 3D GIS: A Time-Geographic Approach.”\nSpatially Integrated Social Science 27: 721–44.\n\n\nLoidl, Martin, Gudrun Wallentin, Robin Wendel, and Bernhard Zagel. 2016.\n“Mapping Bicycle Crash Risk Patterns on the Local Scale.”\nSafety 2 (3): 17. https://doi.org/10.3390/safety2030017.\n\n\nLovelace, Robin, Jakub Nowosad, and Jannes Muenchow. 2019.\nGeocomputation with r. Chapman; Hall/CRC. https://doi.org/10.1201/9780203730058.\n\n\n———. 2024. Geocomputation with r. Online. https://doi.org/10.1201/9780203730058.\n\n\nLu, Binbin, Paul Harris, Martin Charlton, and Chris Brunsdon. 2014.\n“The GWmodel r Package: Further Topics for Exploring Spatial\nHeterogeneity Using Geographically Weighted Models.”\nGeo-Spatial Information Science 17 (2): 85–101.\n\n\nMultilevel Modelling, Centre for. n.d. “Introduction to Multilevel\nModelling.” n.d. http://www.bristol.ac.uk/cmm/learning/online-course/course-topics.html#m04.\n\n\nNiedomysl, Thomas. 2011. “How Migration Motives Change over\nMigration Distance: Evidence on Variation Across Socio-Economic and\nDemographic Groups.” Regional Studies 45 (6): 843–55. https://doi.org/10.1080/00343401003614266.\n\n\nÖnnerfors, Åsa, Mariana Kotzeva, Teodóra Brandmüller, et al. 2019.\n“Eurostat Regional Yearbook 2019 Edition.”\n\n\nOpenshaw, Stan. 1981. “The Modifiable Areal Unit Problem.”\nQuantitative Geography: A British View, 60–69.\n\n\nPatias, Nikos, Francisco Rowe, and Dani Arribas-Bel. 2021.\n“Trajectories of Neighbourhood Inequality in Britain: Unpacking\nInter-Regional Socioeconomic Imbalances,\n1971-2011.” The Geographical Journal 188\n(2): 150–65. https://doi.org/10.1111/geoj.12420.\n\n\nPatias, Nikos, Francisco Rowe, and Stefano Cavazzi. 2019. “A\nScalable Analytical Framework for Spatio-Temporal Analysis of\nNeighborhood Change: A Sequence Analysis Approach.” In, 223–41.\nSpringer International Publishing. https://doi.org/10.1007/978-3-030-14745-7_13.\n\n\nPebesma, Edzer. 2004. “Multivariable Geostatistics in S: The Gstat\nPackage.” Computers & Geosciences 30 (7): 683–91. https://doi.org/10.1016/j.cageo.2004.03.012.\n\n\nPebesma, Edzer et al. 2012. “Spacetime: Spatio-Temporal Data in\nr.” Journal of Statistical Software 51 (7): 1–30.\n\n\nPebesma, Edzer. 2018. “Simple Features for R:\nStandardized Support for Spatial Vector Data.”\nThe R Journal 10 (1): 439–46. https://doi.org/10.32614/RJ-2018-009.\n\n\n———. 2022a. Sf: Simple Features for r. https://CRAN.R-project.org/package=sf.\n\n\n———. 2022b. Stars: Spatiotemporal Arrays, Raster and Vector Data\nCubes. https://CRAN.R-project.org/package=stars.\n\n\n———. 2023. Lwgeom: Bindings to Selected Liblwgeom Functions for\nSimple Features. https://github.com/r-spatial/lwgeom/.\n\n\nPebesma, Edzer, and Roger Bivand. 2023. Spatial Data Science: With\nApplications in r. CRC Press.\n\n\nPebesma, Edzer, and Benedikt Graeler. 2022. Gstat: Spatial and\nSpatio-Temporal Geostatistical Modelling, Prediction and\nSimulation. https://github.com/r-spatial/gstat/.\n\n\nRey, Sergio J., Daniel Arribas-Bel, and Levi J. Wolf. 2023.\nGeographic Data Science with PySAL and the PyData Stack. CRC\npress.\n\n\nRobinson, WS. 1950. “Ecological Correlations and Individual\nBehavior.” American Sociological Review 15 (195):\n351–57.\n\n\nRowe, Francisco. 2022. “Introduction to Geographic Data\nScience.” Open Science Framework, August. https://doi.org/10.17605/OSF.IO/VHY2P.\n\n\nRowe, Francisco, Robin Lovelace, and Adam Dennett. 2022. “Spatial\nInteraction Modelling: A Manifesto.” http://dx.doi.org/10.31219/osf.io/xcdms.\n\n\nRowe, Francisco, and Nikos Patias. 2020. “Mapping the Spatial\nPatterns of Internal Migration in Europe.” Regional Studies,\nRegional Science 7 (1): 390–93. https://doi.org/10.1080/21681376.2020.1811139.\n\n\nSingleton, Alex. 2017. “Geographic Data Science for Urban\nAnalytics.” http://www.alex-singleton.com/GDS_UA_2017/.\n\n\nSingleton, Alexander D., and Seth E. Spielman. 2013. “The Past,\nPresent, and Future of Geodemographic Research in the United States and\nUnited Kingdom.” The Professional Geographer 66 (4):\n558–67. https://doi.org/10.1080/00330124.2013.848764.\n\n\nStillwell, John, Konstantinos Daras, and Martin Bell. 2018.\n“Spatial Aggregation Methods for Investigating the MAUP Effects in\nMigration Analysis.” Applied Spatial Analysis and Policy\n11 (4): 693–711. https://doi.org/10.1007/s12061-018-9274-6.\n\n\nTao, Sui, Jonathan Corcoran, Francisco Rowe, and Mark Hickman. 2018.\n“To Travel or Not to Travel: ‘Weather’\nIs the Question. Modelling the Effect of Local Weather Conditions on Bus\nRidership.” Transportation Research Part C: Emerging\nTechnologies 86 (January): 147–67. https://doi.org/10.1016/j.trc.2017.11.005.\n\n\nTennekes, Martijn. 2018. “tmap:\nThematic Maps in R.” Journal of Statistical\nSoftware 84 (6): 1–39. https://doi.org/10.18637/jss.v084.i06.\n\n\n———. 2022. Tmap: Thematic Maps. https://github.com/r-tmap/tmap.\n\n\nWheeler, David, and Michael Tiefelsdorf. 2005. “Multicollinearity\nand Correlation Among Local Regression Coefficients in Geographically\nWeighted Regression.” Journal of Geographical Systems 7\n(2): 161–87.\n\n\nWickham, Hadley. 2009. Ggplot2. Springer New York. https://doi.org/10.1007/978-0-387-98141-3.\n\n\nWickham, Hadley, Mara Averick, Jennifer Bryan, Winston Chang, Lucy\nD’Agostino McGowan, Romain François, Garrett Grolemund, et al. 2019.\n“Welcome to the tidyverse.”\nJournal of Open Source Software 4 (43): 1686. https://doi.org/10.21105/joss.01686.\n\n\nWickham, Hadley, Mine Çetinkaya-Rundel, and Garrett Grolemund. 2023.\nR for Data Science. \" O’Reilly Media, Inc.\".\n\n\nWikle, Christopher K, Andrew Zammit-Mangion, and Noel Cressie. 2019.\nSpatio-Temporal Statistics with r. CRC Press.\n\n\nWilliamson, Paul. 2018. “Survey Analysis.”\n\n\nWolf, Levi John, Sean Fox, Rich Harris, Ron Johnston, Kelvyn Jones,\nDavid Manley, Emmanouil Tranos, and Wenfei Winnie Wang. 2020.\n“Quantitative Geography III: Future Challenges and Challenging\nFutures.” Progress in Human Geography 45 (3): 596–608.\nhttps://doi.org/10.1177/0309132520924722.\n\n\nXie, Yihui, JJ Allaire, and Garrett Grolemund. 2019. R Markdown: The\nDefinitive Guide. CRC Press, Taylor & Francis, Chapman &\nHall Book. https://bookdown.org/yihui/rmarkdown/.\n\n\nZammit-Mangion, Andrew, and Noel Cressie. 2017. “FRK: An r Package\nfor Spatial and Spatio-Temporal Prediction with Large Datasets.”\narXiv Preprint arXiv:1705.08105.", + "crumbs": [ + "References" + ] } ] \ No newline at end of file diff --git a/docs/site_libs/Proj4Leaflet-1.0.1/proj4leaflet.js b/docs/site_libs/Proj4Leaflet-1.0.1/proj4leaflet.js new file mode 100644 index 0000000..eaa650c --- /dev/null +++ b/docs/site_libs/Proj4Leaflet-1.0.1/proj4leaflet.js @@ -0,0 +1,272 @@ +(function (factory) { + var L, proj4; + if (typeof define === 'function' && define.amd) { + // AMD + define(['leaflet', 'proj4'], factory); + } else if (typeof module === 'object' && typeof module.exports === "object") { + // Node/CommonJS + L = require('leaflet'); + proj4 = require('proj4'); + module.exports = factory(L, proj4); + } else { + // Browser globals + if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined') + throw 'Leaflet and proj4 must be loaded first'; + factory(window.L, window.proj4); + } +}(function (L, proj4) { + if (proj4.__esModule && proj4.default) { + // If proj4 was bundled as an ES6 module, unwrap it to get + // to the actual main proj4 object. + // See discussion in https://github.com/kartena/Proj4Leaflet/pull/147 + proj4 = proj4.default; + } + + L.Proj = {}; + + L.Proj._isProj4Obj = function(a) { + return (typeof a.inverse !== 'undefined' && + typeof a.forward !== 'undefined'); + }; + + L.Proj.Projection = L.Class.extend({ + initialize: function(code, def, bounds) { + var isP4 = L.Proj._isProj4Obj(code); + this._proj = isP4 ? code : this._projFromCodeDef(code, def); + this.bounds = isP4 ? def : bounds; + }, + + project: function (latlng) { + var point = this._proj.forward([latlng.lng, latlng.lat]); + return new L.Point(point[0], point[1]); + }, + + unproject: function (point, unbounded) { + var point2 = this._proj.inverse([point.x, point.y]); + return new L.LatLng(point2[1], point2[0], unbounded); + }, + + _projFromCodeDef: function(code, def) { + if (def) { + proj4.defs(code, def); + } else if (proj4.defs[code] === undefined) { + var urn = code.split(':'); + if (urn.length > 3) { + code = urn[urn.length - 3] + ':' + urn[urn.length - 1]; + } + if (proj4.defs[code] === undefined) { + throw 'No projection definition for code ' + code; + } + } + + return proj4(code); + } + }); + + L.Proj.CRS = L.Class.extend({ + includes: L.CRS, + + options: { + transformation: new L.Transformation(1, 0, -1, 0) + }, + + initialize: function(a, b, c) { + var code, + proj, + def, + options; + + if (L.Proj._isProj4Obj(a)) { + proj = a; + code = proj.srsCode; + options = b || {}; + + this.projection = new L.Proj.Projection(proj, options.bounds); + } else { + code = a; + def = b; + options = c || {}; + this.projection = new L.Proj.Projection(code, def, options.bounds); + } + + L.Util.setOptions(this, options); + this.code = code; + this.transformation = this.options.transformation; + + if (this.options.origin) { + this.transformation = + new L.Transformation(1, -this.options.origin[0], + -1, this.options.origin[1]); + } + + if (this.options.scales) { + this._scales = this.options.scales; + } else if (this.options.resolutions) { + this._scales = []; + for (var i = this.options.resolutions.length - 1; i >= 0; i--) { + if (this.options.resolutions[i]) { + this._scales[i] = 1 / this.options.resolutions[i]; + } + } + } + + this.infinite = !this.options.bounds; + + }, + + scale: function(zoom) { + var iZoom = Math.floor(zoom), + baseScale, + nextScale, + scaleDiff, + zDiff; + if (zoom === iZoom) { + return this._scales[zoom]; + } else { + // Non-integer zoom, interpolate + baseScale = this._scales[iZoom]; + nextScale = this._scales[iZoom + 1]; + scaleDiff = nextScale - baseScale; + zDiff = (zoom - iZoom); + return baseScale + scaleDiff * zDiff; + } + }, + + zoom: function(scale) { + // Find closest number in this._scales, down + var downScale = this._closestElement(this._scales, scale), + downZoom = this._scales.indexOf(downScale), + nextScale, + nextZoom, + scaleDiff; + // Check if scale is downScale => return array index + if (scale === downScale) { + return downZoom; + } + if (downScale === undefined) { + return -Infinity; + } + // Interpolate + nextZoom = downZoom + 1; + nextScale = this._scales[nextZoom]; + if (nextScale === undefined) { + return Infinity; + } + scaleDiff = nextScale - downScale; + return (scale - downScale) / scaleDiff + downZoom; + }, + + distance: L.CRS.Earth.distance, + + R: L.CRS.Earth.R, + + /* Get the closest lowest element in an array */ + _closestElement: function(array, element) { + var low; + for (var i = array.length; i--;) { + if (array[i] <= element && (low === undefined || low < array[i])) { + low = array[i]; + } + } + return low; + } + }); + + L.Proj.GeoJSON = L.GeoJSON.extend({ + initialize: function(geojson, options) { + this._callLevel = 0; + L.GeoJSON.prototype.initialize.call(this, geojson, options); + }, + + addData: function(geojson) { + var crs; + + if (geojson) { + if (geojson.crs && geojson.crs.type === 'name') { + crs = new L.Proj.CRS(geojson.crs.properties.name); + } else if (geojson.crs && geojson.crs.type) { + crs = new L.Proj.CRS(geojson.crs.type + ':' + geojson.crs.properties.code); + } + + if (crs !== undefined) { + this.options.coordsToLatLng = function(coords) { + var point = L.point(coords[0], coords[1]); + return crs.projection.unproject(point); + }; + } + } + + // Base class' addData might call us recursively, but + // CRS shouldn't be cleared in that case, since CRS applies + // to the whole GeoJSON, inluding sub-features. + this._callLevel++; + try { + L.GeoJSON.prototype.addData.call(this, geojson); + } finally { + this._callLevel--; + if (this._callLevel === 0) { + delete this.options.coordsToLatLng; + } + } + } + }); + + L.Proj.geoJson = function(geojson, options) { + return new L.Proj.GeoJSON(geojson, options); + }; + + L.Proj.ImageOverlay = L.ImageOverlay.extend({ + initialize: function (url, bounds, options) { + L.ImageOverlay.prototype.initialize.call(this, url, null, options); + this._projectedBounds = bounds; + }, + + // Danger ahead: Overriding internal methods in Leaflet. + // Decided to do this rather than making a copy of L.ImageOverlay + // and doing very tiny modifications to it. + // Future will tell if this was wise or not. + _animateZoom: function (event) { + var scale = this._map.getZoomScale(event.zoom); + var northWest = L.point(this._projectedBounds.min.x, this._projectedBounds.max.y); + var offset = this._projectedToNewLayerPoint(northWest, event.zoom, event.center); + + L.DomUtil.setTransform(this._image, offset, scale); + }, + + _reset: function () { + var zoom = this._map.getZoom(); + var pixelOrigin = this._map.getPixelOrigin(); + var bounds = L.bounds( + this._transform(this._projectedBounds.min, zoom)._subtract(pixelOrigin), + this._transform(this._projectedBounds.max, zoom)._subtract(pixelOrigin) + ); + var size = bounds.getSize(); + + L.DomUtil.setPosition(this._image, bounds.min); + this._image.style.width = size.x + 'px'; + this._image.style.height = size.y + 'px'; + }, + + _projectedToNewLayerPoint: function (point, zoom, center) { + var viewHalf = this._map.getSize()._divideBy(2); + var newTopLeft = this._map.project(center, zoom)._subtract(viewHalf)._round(); + var topLeft = newTopLeft.add(this._map._getMapPanePos()); + + return this._transform(point, zoom)._subtract(topLeft); + }, + + _transform: function (point, zoom) { + var crs = this._map.options.crs; + var transformation = crs.transformation; + var scale = crs.scale(zoom); + + return transformation.transform(point, scale); + } + }); + + L.Proj.imageOverlay = function (url, bounds, options) { + return new L.Proj.ImageOverlay(url, bounds, options); + }; + + return L.Proj; +})); diff --git a/docs/site_libs/bootstrap/bootstrap-icons.css b/docs/site_libs/bootstrap/bootstrap-icons.css index f51d04b..285e444 100644 --- a/docs/site_libs/bootstrap/bootstrap-icons.css +++ b/docs/site_libs/bootstrap/bootstrap-icons.css @@ -1,7 +1,14 @@ +/*! + * Bootstrap Icons v1.11.1 (https://icons.getbootstrap.com/) + * Copyright 2019-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) + */ + @font-face { + font-display: block; font-family: "bootstrap-icons"; src: -url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); +url("./bootstrap-icons.woff?2820a3852bdb9a5832199cc61cec4e65") format("woff"); } .bi::before, @@ -440,7 +447,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-cloud-fog2::before { content: "\f2a2"; } .bi-cloud-hail-fill::before { content: "\f2a3"; } .bi-cloud-hail::before { content: "\f2a4"; } -.bi-cloud-haze-1::before { content: "\f2a5"; } .bi-cloud-haze-fill::before { content: "\f2a6"; } .bi-cloud-haze::before { content: "\f2a7"; } .bi-cloud-haze2-fill::before { content: "\f2a8"; } @@ -1436,21 +1442,16 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-dpad::before { content: "\f687"; } .bi-ear-fill::before { content: "\f688"; } .bi-ear::before { content: "\f689"; } -.bi-envelope-check-1::before { content: "\f68a"; } .bi-envelope-check-fill::before { content: "\f68b"; } .bi-envelope-check::before { content: "\f68c"; } -.bi-envelope-dash-1::before { content: "\f68d"; } .bi-envelope-dash-fill::before { content: "\f68e"; } .bi-envelope-dash::before { content: "\f68f"; } -.bi-envelope-exclamation-1::before { content: "\f690"; } .bi-envelope-exclamation-fill::before { content: "\f691"; } .bi-envelope-exclamation::before { content: "\f692"; } .bi-envelope-plus-fill::before { content: "\f693"; } .bi-envelope-plus::before { content: "\f694"; } -.bi-envelope-slash-1::before { content: "\f695"; } .bi-envelope-slash-fill::before { content: "\f696"; } .bi-envelope-slash::before { content: "\f697"; } -.bi-envelope-x-1::before { content: "\f698"; } .bi-envelope-x-fill::before { content: "\f699"; } .bi-envelope-x::before { content: "\f69a"; } .bi-explicit-fill::before { content: "\f69b"; } @@ -1460,8 +1461,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-list-columns-reverse::before { content: "\f69f"; } .bi-list-columns::before { content: "\f6a0"; } .bi-meta::before { content: "\f6a1"; } -.bi-mortorboard-fill::before { content: "\f6a2"; } -.bi-mortorboard::before { content: "\f6a3"; } .bi-nintendo-switch::before { content: "\f6a4"; } .bi-pc-display-horizontal::before { content: "\f6a5"; } .bi-pc-display::before { content: "\f6a6"; } @@ -1480,7 +1479,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-send-check::before { content: "\f6b3"; } .bi-send-dash-fill::before { content: "\f6b4"; } .bi-send-dash::before { content: "\f6b5"; } -.bi-send-exclamation-1::before { content: "\f6b6"; } .bi-send-exclamation-fill::before { content: "\f6b7"; } .bi-send-exclamation::before { content: "\f6b8"; } .bi-send-fill::before { content: "\f6b9"; } @@ -1492,7 +1490,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-send-x::before { content: "\f6bf"; } .bi-send::before { content: "\f6c0"; } .bi-steam::before { content: "\f6c1"; } -.bi-terminal-dash-1::before { content: "\f6c2"; } .bi-terminal-dash::before { content: "\f6c3"; } .bi-terminal-plus::before { content: "\f6c4"; } .bi-terminal-split::before { content: "\f6c5"; } @@ -1522,7 +1519,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-usb-symbol::before { content: "\f6dd"; } .bi-usb::before { content: "\f6de"; } .bi-boombox-fill::before { content: "\f6df"; } -.bi-displayport-1::before { content: "\f6e0"; } .bi-displayport::before { content: "\f6e1"; } .bi-gpu-card::before { content: "\f6e2"; } .bi-memory::before { content: "\f6e3"; } @@ -1535,8 +1531,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-pci-card::before { content: "\f6ea"; } .bi-router-fill::before { content: "\f6eb"; } .bi-router::before { content: "\f6ec"; } -.bi-ssd-fill::before { content: "\f6ed"; } -.bi-ssd::before { content: "\f6ee"; } .bi-thunderbolt-fill::before { content: "\f6ef"; } .bi-thunderbolt::before { content: "\f6f0"; } .bi-usb-drive-fill::before { content: "\f6f1"; } @@ -1643,7 +1637,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-filetype-pdf::before { content: "\f756"; } .bi-filetype-php::before { content: "\f757"; } .bi-filetype-png::before { content: "\f758"; } -.bi-filetype-ppt-1::before { content: "\f759"; } .bi-filetype-ppt::before { content: "\f75a"; } .bi-filetype-psd::before { content: "\f75b"; } .bi-filetype-py::before { content: "\f75c"; } @@ -1659,7 +1652,6 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-filetype-txt::before { content: "\f766"; } .bi-filetype-wav::before { content: "\f767"; } .bi-filetype-woff::before { content: "\f768"; } -.bi-filetype-xls-1::before { content: "\f769"; } .bi-filetype-xls::before { content: "\f76a"; } .bi-filetype-xml::before { content: "\f76b"; } .bi-filetype-yml::before { content: "\f76c"; } @@ -1702,3 +1694,385 @@ url("./bootstrap-icons.woff?524846017b983fc8ded9325d94ed40f3") format("woff"); .bi-filetype-json::before { content: "\f791"; } .bi-filetype-pptx::before { content: "\f792"; } .bi-filetype-xlsx::before { content: "\f793"; } +.bi-1-circle-fill::before { content: "\f796"; } +.bi-1-circle::before { content: "\f797"; } +.bi-1-square-fill::before { content: "\f798"; } +.bi-1-square::before { content: "\f799"; } +.bi-2-circle-fill::before { content: "\f79c"; } +.bi-2-circle::before { content: "\f79d"; } +.bi-2-square-fill::before { content: "\f79e"; } +.bi-2-square::before { content: "\f79f"; } +.bi-3-circle-fill::before { content: "\f7a2"; } +.bi-3-circle::before { content: "\f7a3"; } +.bi-3-square-fill::before { content: "\f7a4"; } +.bi-3-square::before { content: "\f7a5"; } +.bi-4-circle-fill::before { content: "\f7a8"; } +.bi-4-circle::before { content: "\f7a9"; } +.bi-4-square-fill::before { content: "\f7aa"; } +.bi-4-square::before { content: "\f7ab"; } +.bi-5-circle-fill::before { content: "\f7ae"; } +.bi-5-circle::before { content: "\f7af"; } +.bi-5-square-fill::before { content: "\f7b0"; } +.bi-5-square::before { content: "\f7b1"; } +.bi-6-circle-fill::before { content: "\f7b4"; } +.bi-6-circle::before { content: "\f7b5"; } +.bi-6-square-fill::before { content: "\f7b6"; } +.bi-6-square::before { content: "\f7b7"; } +.bi-7-circle-fill::before { content: "\f7ba"; } +.bi-7-circle::before { content: "\f7bb"; } +.bi-7-square-fill::before { content: "\f7bc"; } +.bi-7-square::before { content: "\f7bd"; } +.bi-8-circle-fill::before { content: "\f7c0"; } +.bi-8-circle::before { content: "\f7c1"; } +.bi-8-square-fill::before { content: "\f7c2"; } +.bi-8-square::before { content: "\f7c3"; } +.bi-9-circle-fill::before { content: "\f7c6"; } +.bi-9-circle::before { content: "\f7c7"; } +.bi-9-square-fill::before { content: "\f7c8"; } +.bi-9-square::before { content: "\f7c9"; } +.bi-airplane-engines-fill::before { content: "\f7ca"; } +.bi-airplane-engines::before { content: "\f7cb"; } +.bi-airplane-fill::before { content: "\f7cc"; } +.bi-airplane::before { content: "\f7cd"; } +.bi-alexa::before { content: "\f7ce"; } +.bi-alipay::before { content: "\f7cf"; } +.bi-android::before { content: "\f7d0"; } +.bi-android2::before { content: "\f7d1"; } +.bi-box-fill::before { content: "\f7d2"; } +.bi-box-seam-fill::before { content: "\f7d3"; } +.bi-browser-chrome::before { content: "\f7d4"; } +.bi-browser-edge::before { content: "\f7d5"; } +.bi-browser-firefox::before { content: "\f7d6"; } +.bi-browser-safari::before { content: "\f7d7"; } +.bi-c-circle-fill::before { content: "\f7da"; } +.bi-c-circle::before { content: "\f7db"; } +.bi-c-square-fill::before { content: "\f7dc"; } +.bi-c-square::before { content: "\f7dd"; } +.bi-capsule-pill::before { content: "\f7de"; } +.bi-capsule::before { content: "\f7df"; } +.bi-car-front-fill::before { content: "\f7e0"; } +.bi-car-front::before { content: "\f7e1"; } +.bi-cassette-fill::before { content: "\f7e2"; } +.bi-cassette::before { content: "\f7e3"; } +.bi-cc-circle-fill::before { content: "\f7e6"; } +.bi-cc-circle::before { content: "\f7e7"; } +.bi-cc-square-fill::before { content: "\f7e8"; } +.bi-cc-square::before { content: "\f7e9"; } +.bi-cup-hot-fill::before { content: "\f7ea"; } +.bi-cup-hot::before { content: "\f7eb"; } +.bi-currency-rupee::before { content: "\f7ec"; } +.bi-dropbox::before { content: "\f7ed"; } +.bi-escape::before { content: "\f7ee"; } +.bi-fast-forward-btn-fill::before { content: "\f7ef"; } +.bi-fast-forward-btn::before { content: "\f7f0"; } +.bi-fast-forward-circle-fill::before { content: "\f7f1"; } +.bi-fast-forward-circle::before { content: "\f7f2"; } +.bi-fast-forward-fill::before { content: "\f7f3"; } +.bi-fast-forward::before { content: "\f7f4"; } +.bi-filetype-sql::before { content: "\f7f5"; } +.bi-fire::before { content: "\f7f6"; } +.bi-google-play::before { content: "\f7f7"; } +.bi-h-circle-fill::before { content: "\f7fa"; } +.bi-h-circle::before { content: "\f7fb"; } +.bi-h-square-fill::before { content: "\f7fc"; } +.bi-h-square::before { content: "\f7fd"; } +.bi-indent::before { content: "\f7fe"; } +.bi-lungs-fill::before { content: "\f7ff"; } +.bi-lungs::before { content: "\f800"; } +.bi-microsoft-teams::before { content: "\f801"; } +.bi-p-circle-fill::before { content: "\f804"; } +.bi-p-circle::before { content: "\f805"; } +.bi-p-square-fill::before { content: "\f806"; } +.bi-p-square::before { content: "\f807"; } +.bi-pass-fill::before { content: "\f808"; } +.bi-pass::before { content: "\f809"; } +.bi-prescription::before { content: "\f80a"; } +.bi-prescription2::before { content: "\f80b"; } +.bi-r-circle-fill::before { content: "\f80e"; } +.bi-r-circle::before { content: "\f80f"; } +.bi-r-square-fill::before { content: "\f810"; } +.bi-r-square::before { content: "\f811"; } +.bi-repeat-1::before { content: "\f812"; } +.bi-repeat::before { content: "\f813"; } +.bi-rewind-btn-fill::before { content: "\f814"; } +.bi-rewind-btn::before { content: "\f815"; } +.bi-rewind-circle-fill::before { content: "\f816"; } +.bi-rewind-circle::before { content: "\f817"; } +.bi-rewind-fill::before { content: "\f818"; } +.bi-rewind::before { content: "\f819"; } +.bi-train-freight-front-fill::before { content: "\f81a"; } +.bi-train-freight-front::before { content: "\f81b"; } +.bi-train-front-fill::before { content: "\f81c"; } +.bi-train-front::before { content: "\f81d"; } +.bi-train-lightrail-front-fill::before { content: "\f81e"; } +.bi-train-lightrail-front::before { content: "\f81f"; } +.bi-truck-front-fill::before { content: "\f820"; } +.bi-truck-front::before { content: "\f821"; } +.bi-ubuntu::before { content: "\f822"; } +.bi-unindent::before { content: "\f823"; } +.bi-unity::before { content: "\f824"; } +.bi-universal-access-circle::before { content: "\f825"; } +.bi-universal-access::before { content: "\f826"; } +.bi-virus::before { content: "\f827"; } +.bi-virus2::before { content: "\f828"; } +.bi-wechat::before { content: "\f829"; } +.bi-yelp::before { content: "\f82a"; } +.bi-sign-stop-fill::before { content: "\f82b"; } +.bi-sign-stop-lights-fill::before { content: "\f82c"; } +.bi-sign-stop-lights::before { content: "\f82d"; } +.bi-sign-stop::before { content: "\f82e"; } +.bi-sign-turn-left-fill::before { content: "\f82f"; } +.bi-sign-turn-left::before { content: "\f830"; } +.bi-sign-turn-right-fill::before { content: "\f831"; } +.bi-sign-turn-right::before { content: "\f832"; } +.bi-sign-turn-slight-left-fill::before { content: "\f833"; } +.bi-sign-turn-slight-left::before { content: "\f834"; } +.bi-sign-turn-slight-right-fill::before { content: "\f835"; } +.bi-sign-turn-slight-right::before { content: "\f836"; } +.bi-sign-yield-fill::before { content: "\f837"; } +.bi-sign-yield::before { content: "\f838"; } +.bi-ev-station-fill::before { content: "\f839"; } +.bi-ev-station::before { content: "\f83a"; } +.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } +.bi-fuel-pump-diesel::before { content: "\f83c"; } +.bi-fuel-pump-fill::before { content: "\f83d"; } +.bi-fuel-pump::before { content: "\f83e"; } +.bi-0-circle-fill::before { content: "\f83f"; } +.bi-0-circle::before { content: "\f840"; } +.bi-0-square-fill::before { content: "\f841"; } +.bi-0-square::before { content: "\f842"; } +.bi-rocket-fill::before { content: "\f843"; } +.bi-rocket-takeoff-fill::before { content: "\f844"; } +.bi-rocket-takeoff::before { content: "\f845"; } +.bi-rocket::before { content: "\f846"; } +.bi-stripe::before { content: "\f847"; } +.bi-subscript::before { content: "\f848"; } +.bi-superscript::before { content: "\f849"; } +.bi-trello::before { content: "\f84a"; } +.bi-envelope-at-fill::before { content: "\f84b"; } +.bi-envelope-at::before { content: "\f84c"; } +.bi-regex::before { content: "\f84d"; } +.bi-text-wrap::before { content: "\f84e"; } +.bi-sign-dead-end-fill::before { content: "\f84f"; } +.bi-sign-dead-end::before { content: "\f850"; } +.bi-sign-do-not-enter-fill::before { content: "\f851"; } +.bi-sign-do-not-enter::before { content: "\f852"; } +.bi-sign-intersection-fill::before { content: "\f853"; } +.bi-sign-intersection-side-fill::before { content: "\f854"; } +.bi-sign-intersection-side::before { content: "\f855"; } +.bi-sign-intersection-t-fill::before { content: "\f856"; } +.bi-sign-intersection-t::before { content: "\f857"; } +.bi-sign-intersection-y-fill::before { content: "\f858"; } +.bi-sign-intersection-y::before { content: "\f859"; } +.bi-sign-intersection::before { content: "\f85a"; } +.bi-sign-merge-left-fill::before { content: "\f85b"; } +.bi-sign-merge-left::before { content: "\f85c"; } +.bi-sign-merge-right-fill::before { content: "\f85d"; } +.bi-sign-merge-right::before { content: "\f85e"; } +.bi-sign-no-left-turn-fill::before { content: "\f85f"; } +.bi-sign-no-left-turn::before { content: "\f860"; } +.bi-sign-no-parking-fill::before { content: "\f861"; } +.bi-sign-no-parking::before { content: "\f862"; } +.bi-sign-no-right-turn-fill::before { content: "\f863"; } +.bi-sign-no-right-turn::before { content: "\f864"; } +.bi-sign-railroad-fill::before { content: "\f865"; } +.bi-sign-railroad::before { content: "\f866"; } +.bi-building-add::before { content: "\f867"; } +.bi-building-check::before { content: "\f868"; } +.bi-building-dash::before { content: "\f869"; } +.bi-building-down::before { content: "\f86a"; } +.bi-building-exclamation::before { content: "\f86b"; } +.bi-building-fill-add::before { content: "\f86c"; } +.bi-building-fill-check::before { content: "\f86d"; } +.bi-building-fill-dash::before { content: "\f86e"; } +.bi-building-fill-down::before { content: "\f86f"; } +.bi-building-fill-exclamation::before { content: "\f870"; } +.bi-building-fill-gear::before { content: "\f871"; } +.bi-building-fill-lock::before { content: "\f872"; } +.bi-building-fill-slash::before { content: "\f873"; } +.bi-building-fill-up::before { content: "\f874"; } +.bi-building-fill-x::before { content: "\f875"; } +.bi-building-fill::before { content: "\f876"; } +.bi-building-gear::before { content: "\f877"; } +.bi-building-lock::before { content: "\f878"; } +.bi-building-slash::before { content: "\f879"; } +.bi-building-up::before { content: "\f87a"; } +.bi-building-x::before { content: "\f87b"; } +.bi-buildings-fill::before { content: "\f87c"; } +.bi-buildings::before { content: "\f87d"; } +.bi-bus-front-fill::before { content: "\f87e"; } +.bi-bus-front::before { content: "\f87f"; } +.bi-ev-front-fill::before { content: "\f880"; } +.bi-ev-front::before { content: "\f881"; } +.bi-globe-americas::before { content: "\f882"; } +.bi-globe-asia-australia::before { content: "\f883"; } +.bi-globe-central-south-asia::before { content: "\f884"; } +.bi-globe-europe-africa::before { content: "\f885"; } +.bi-house-add-fill::before { content: "\f886"; } +.bi-house-add::before { content: "\f887"; } +.bi-house-check-fill::before { content: "\f888"; } +.bi-house-check::before { content: "\f889"; } +.bi-house-dash-fill::before { content: "\f88a"; } +.bi-house-dash::before { content: "\f88b"; } +.bi-house-down-fill::before { content: "\f88c"; } +.bi-house-down::before { content: "\f88d"; } +.bi-house-exclamation-fill::before { content: "\f88e"; } +.bi-house-exclamation::before { content: "\f88f"; } +.bi-house-gear-fill::before { content: "\f890"; } +.bi-house-gear::before { content: "\f891"; } +.bi-house-lock-fill::before { content: "\f892"; } +.bi-house-lock::before { content: "\f893"; } +.bi-house-slash-fill::before { content: "\f894"; } +.bi-house-slash::before { content: "\f895"; } +.bi-house-up-fill::before { content: "\f896"; } +.bi-house-up::before { content: "\f897"; } +.bi-house-x-fill::before { content: "\f898"; } +.bi-house-x::before { content: "\f899"; } +.bi-person-add::before { content: "\f89a"; } +.bi-person-down::before { content: "\f89b"; } +.bi-person-exclamation::before { content: "\f89c"; } +.bi-person-fill-add::before { content: "\f89d"; } +.bi-person-fill-check::before { content: "\f89e"; } +.bi-person-fill-dash::before { content: "\f89f"; } +.bi-person-fill-down::before { content: "\f8a0"; } +.bi-person-fill-exclamation::before { content: "\f8a1"; } +.bi-person-fill-gear::before { content: "\f8a2"; } +.bi-person-fill-lock::before { content: "\f8a3"; } +.bi-person-fill-slash::before { content: "\f8a4"; } +.bi-person-fill-up::before { content: "\f8a5"; } +.bi-person-fill-x::before { content: "\f8a6"; } +.bi-person-gear::before { content: "\f8a7"; } +.bi-person-lock::before { content: "\f8a8"; } +.bi-person-slash::before { content: "\f8a9"; } +.bi-person-up::before { content: "\f8aa"; } +.bi-scooter::before { content: "\f8ab"; } +.bi-taxi-front-fill::before { content: "\f8ac"; } +.bi-taxi-front::before { content: "\f8ad"; } +.bi-amd::before { content: "\f8ae"; } +.bi-database-add::before { content: "\f8af"; } +.bi-database-check::before { content: "\f8b0"; } +.bi-database-dash::before { content: "\f8b1"; } +.bi-database-down::before { content: "\f8b2"; } +.bi-database-exclamation::before { content: "\f8b3"; } +.bi-database-fill-add::before { content: "\f8b4"; } +.bi-database-fill-check::before { content: "\f8b5"; } +.bi-database-fill-dash::before { content: "\f8b6"; } +.bi-database-fill-down::before { content: "\f8b7"; } +.bi-database-fill-exclamation::before { content: "\f8b8"; } +.bi-database-fill-gear::before { content: "\f8b9"; } +.bi-database-fill-lock::before { content: "\f8ba"; } +.bi-database-fill-slash::before { content: "\f8bb"; } +.bi-database-fill-up::before { content: "\f8bc"; } +.bi-database-fill-x::before { content: "\f8bd"; } +.bi-database-fill::before { content: "\f8be"; } +.bi-database-gear::before { content: "\f8bf"; } +.bi-database-lock::before { content: "\f8c0"; } +.bi-database-slash::before { content: "\f8c1"; } +.bi-database-up::before { content: "\f8c2"; } +.bi-database-x::before { content: "\f8c3"; } +.bi-database::before { content: "\f8c4"; } +.bi-houses-fill::before { content: "\f8c5"; } +.bi-houses::before { content: "\f8c6"; } +.bi-nvidia::before { content: "\f8c7"; } +.bi-person-vcard-fill::before { content: "\f8c8"; } +.bi-person-vcard::before { content: "\f8c9"; } +.bi-sina-weibo::before { content: "\f8ca"; } +.bi-tencent-qq::before { content: "\f8cb"; } +.bi-wikipedia::before { content: "\f8cc"; } +.bi-alphabet-uppercase::before { content: "\f2a5"; } +.bi-alphabet::before { content: "\f68a"; } +.bi-amazon::before { content: "\f68d"; } +.bi-arrows-collapse-vertical::before { content: "\f690"; } +.bi-arrows-expand-vertical::before { content: "\f695"; } +.bi-arrows-vertical::before { content: "\f698"; } +.bi-arrows::before { content: "\f6a2"; } +.bi-ban-fill::before { content: "\f6a3"; } +.bi-ban::before { content: "\f6b6"; } +.bi-bing::before { content: "\f6c2"; } +.bi-cake::before { content: "\f6e0"; } +.bi-cake2::before { content: "\f6ed"; } +.bi-cookie::before { content: "\f6ee"; } +.bi-copy::before { content: "\f759"; } +.bi-crosshair::before { content: "\f769"; } +.bi-crosshair2::before { content: "\f794"; } +.bi-emoji-astonished-fill::before { content: "\f795"; } +.bi-emoji-astonished::before { content: "\f79a"; } +.bi-emoji-grimace-fill::before { content: "\f79b"; } +.bi-emoji-grimace::before { content: "\f7a0"; } +.bi-emoji-grin-fill::before { content: "\f7a1"; } +.bi-emoji-grin::before { content: "\f7a6"; } +.bi-emoji-surprise-fill::before { content: "\f7a7"; } +.bi-emoji-surprise::before { content: "\f7ac"; } +.bi-emoji-tear-fill::before { content: "\f7ad"; } +.bi-emoji-tear::before { content: "\f7b2"; } +.bi-envelope-arrow-down-fill::before { content: "\f7b3"; } +.bi-envelope-arrow-down::before { content: "\f7b8"; } +.bi-envelope-arrow-up-fill::before { content: "\f7b9"; } +.bi-envelope-arrow-up::before { content: "\f7be"; } +.bi-feather::before { content: "\f7bf"; } +.bi-feather2::before { content: "\f7c4"; } +.bi-floppy-fill::before { content: "\f7c5"; } +.bi-floppy::before { content: "\f7d8"; } +.bi-floppy2-fill::before { content: "\f7d9"; } +.bi-floppy2::before { content: "\f7e4"; } +.bi-gitlab::before { content: "\f7e5"; } +.bi-highlighter::before { content: "\f7f8"; } +.bi-marker-tip::before { content: "\f802"; } +.bi-nvme-fill::before { content: "\f803"; } +.bi-nvme::before { content: "\f80c"; } +.bi-opencollective::before { content: "\f80d"; } +.bi-pci-card-network::before { content: "\f8cd"; } +.bi-pci-card-sound::before { content: "\f8ce"; } +.bi-radar::before { content: "\f8cf"; } +.bi-send-arrow-down-fill::before { content: "\f8d0"; } +.bi-send-arrow-down::before { content: "\f8d1"; } +.bi-send-arrow-up-fill::before { content: "\f8d2"; } +.bi-send-arrow-up::before { content: "\f8d3"; } +.bi-sim-slash-fill::before { content: "\f8d4"; } +.bi-sim-slash::before { content: "\f8d5"; } +.bi-sourceforge::before { content: "\f8d6"; } +.bi-substack::before { content: "\f8d7"; } +.bi-threads-fill::before { content: "\f8d8"; } +.bi-threads::before { content: "\f8d9"; } +.bi-transparency::before { content: "\f8da"; } +.bi-twitter-x::before { content: "\f8db"; } +.bi-type-h4::before { content: "\f8dc"; } +.bi-type-h5::before { content: "\f8dd"; } +.bi-type-h6::before { content: "\f8de"; } +.bi-backpack-fill::before { content: "\f8df"; } +.bi-backpack::before { content: "\f8e0"; } +.bi-backpack2-fill::before { content: "\f8e1"; } +.bi-backpack2::before { content: "\f8e2"; } +.bi-backpack3-fill::before { content: "\f8e3"; } +.bi-backpack3::before { content: "\f8e4"; } +.bi-backpack4-fill::before { content: "\f8e5"; } +.bi-backpack4::before { content: "\f8e6"; } +.bi-brilliance::before { content: "\f8e7"; } +.bi-cake-fill::before { content: "\f8e8"; } +.bi-cake2-fill::before { content: "\f8e9"; } +.bi-duffle-fill::before { content: "\f8ea"; } +.bi-duffle::before { content: "\f8eb"; } +.bi-exposure::before { content: "\f8ec"; } +.bi-gender-neuter::before { content: "\f8ed"; } +.bi-highlights::before { content: "\f8ee"; } +.bi-luggage-fill::before { content: "\f8ef"; } +.bi-luggage::before { content: "\f8f0"; } +.bi-mailbox-flag::before { content: "\f8f1"; } +.bi-mailbox2-flag::before { content: "\f8f2"; } +.bi-noise-reduction::before { content: "\f8f3"; } +.bi-passport-fill::before { content: "\f8f4"; } +.bi-passport::before { content: "\f8f5"; } +.bi-person-arms-up::before { content: "\f8f6"; } +.bi-person-raised-hand::before { content: "\f8f7"; } +.bi-person-standing-dress::before { content: "\f8f8"; } +.bi-person-standing::before { content: "\f8f9"; } +.bi-person-walking::before { content: "\f8fa"; } +.bi-person-wheelchair::before { content: "\f8fb"; } +.bi-shadows::before { content: "\f8fc"; } +.bi-suitcase-fill::before { content: "\f8fd"; } +.bi-suitcase-lg-fill::before { content: "\f8fe"; } +.bi-suitcase-lg::before { content: "\f8ff"; } +.bi-suitcase::before { content: "\f900"; } +.bi-suitcase2-fill::before { content: "\f901"; } +.bi-suitcase2::before { content: "\f902"; } +.bi-vignette::before { content: "\f903"; } diff --git a/docs/site_libs/bootstrap/bootstrap-icons.woff b/docs/site_libs/bootstrap/bootstrap-icons.woff index b26ccd1..dbeeb05 100644 Binary files a/docs/site_libs/bootstrap/bootstrap-icons.woff and b/docs/site_libs/bootstrap/bootstrap-icons.woff differ diff --git a/docs/site_libs/bootstrap/bootstrap.min.css b/docs/site_libs/bootstrap/bootstrap.min.css index da81b75..a8ae627 100644 --- a/docs/site_libs/bootstrap/bootstrap.min.css +++ b/docs/site_libs/bootstrap/bootstrap.min.css @@ -1,10 +1,12 @@ -/*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors - * Copyright 2011-2021 Twitter, Inc. +@import"https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap";@import"https://fonts.googleapis.com/css?family=Roboto+Mono|Fira+Mono&display=swap";div.csl-entry{clear:both;margin-bottom:10px}.nav-footer-center{min-height:0em !important}.bi-heart-fill{color:darkred}.sidebar-navigation li a{line-height:1.75;color:var(--bs-body-color);font-size:14px}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{background-color:rgba(233,236,239,.65) !important}pre code{padding-left:5%}pre code.sourceCode{padding-left:0}.downlit code a:any-link{text-decoration:underline;text-decoration-color:#ccc}/*! + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) - */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #ffffff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #dee2e6;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-default-rgb: 222, 226, 230;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg-rgb: 255, 255, 255;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 18px;--bs-body-font-family: var(--bs-font-sans-serif);--bs-body-font-size: 1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.5;--bs-body-color: #212529;--bs-body-bg: #ffffff}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.345rem + 1.14vw)}@media(min-width: 1200px){h1,.h1{font-size:2.2rem}}h2,.h2{font-size:calc(1.3rem + 0.6vw)}@media(min-width: 1200px){h2,.h2{font-size:1.75rem}}h3,.h3{font-size:calc(1.275rem + 0.3vw)}@media(min-width: 1200px){h3,.h3{font-size:1.5rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:#f6f6f6;padding:.5rem;border:1px solid #dee2e6;border-radius:.25rem}pre code{background-color:transparent;font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:#9753b8;background-color:#f6f6f6;border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2em}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:#6c757d}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-bg: transparent;--bs-table-accent-bg: transparent;--bs-table-striped-color: #212529;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #212529;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #212529;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #cfe2ff;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg: #fff3cd;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #ffffff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #ffffff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #ffffff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px);padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2em}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;color-adjust:exact;-webkit-print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-inline,.shiny-input-container .checkbox-inline,.shiny-input-container .radio-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#198754;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:1}.was-validated .input-group .form-control:valid:focus,.input-group .form-control.is-valid:focus,.was-validated .input-group .form-select:valid:focus,.input-group .form-select.is-valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:2}.was-validated .input-group .form-control:invalid:focus,.input-group .form-control.is-invalid:focus,.was-validated .input-group .form-select:invalid:focus,.input-group .form-select.is-invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-default{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-default:hover{color:#000;background-color:#e3e6ea;border-color:#e1e5e9}.btn-check:focus+.btn-default,.btn-default:focus{color:#000;background-color:#e3e6ea;border-color:#e1e5e9;box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-check:checked+.btn-default,.btn-check:active+.btn-default,.btn-default:active,.btn-default.active,.show>.btn-default.dropdown-toggle{color:#000;background-color:#e5e8eb;border-color:#e1e5e9}.btn-check:checked+.btn-default:focus,.btn-check:active+.btn-default:focus,.btn-default:active:focus,.btn-default.active:focus,.show>.btn-default.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(189,192,196,.5)}.btn-default:disabled,.btn-default.disabled{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info:disabled,.btn-info.disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-default{color:#dee2e6;border-color:#dee2e6;background-color:transparent}.btn-outline-default:hover{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:focus+.btn-outline-default,.btn-outline-default:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-check:checked+.btn-outline-default,.btn-check:active+.btn-outline-default,.btn-outline-default:active,.btn-outline-default.active,.btn-outline-default.dropdown-toggle.show{color:#000;background-color:#dee2e6;border-color:#dee2e6}.btn-check:checked+.btn-outline-default:focus,.btn-check:active+.btn-outline-default:focus,.btn-outline-default:active:focus,.btn-outline-default.active:focus,.btn-outline-default.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(222,226,230,.5)}.btn-outline-default:disabled,.btn-outline-default.disabled{color:#dee2e6;background-color:transparent}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd;background-color:transparent}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d;background-color:transparent}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754;background-color:transparent}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0;background-color:transparent}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107;background-color:transparent}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545;background-color:transparent}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa;background-color:transparent}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529;background-color:transparent}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:0.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container-xxl,.navbar>.container-xl,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container,.navbar>.container-fluid{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-top,.navbar-expand-sm .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-top,.navbar-expand-md .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-top,.navbar-expand-lg .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-top,.navbar-expand-xl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-top,.navbar-expand-xxl .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;-webkit-flex-grow:1;visibility:visible !important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-top,.navbar-expand .offcanvas-bottom{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-light{background-color:#0d6efd}.navbar-light .navbar-brand{color:#fdfeff}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#fdfeff}.navbar-light .navbar-nav .nav-link{color:#fdfeff}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:#fdfeff}.navbar-light .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:#fdfeff}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#fdfeff}.navbar-dark{background-color:#0d6efd}.navbar-dark .navbar-brand{color:#fdfeff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fdfeff}.navbar-dark .navbar-nav .nav-link{color:#fdfeff}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(253,254,255,.8)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(253,254,255,.75)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fdfeff}.navbar-dark .navbar-toggler{color:#fdfeff;border-color:rgba(253,254,255,.4)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfeff' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:#fdfeff}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fdfeff}.card{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-0.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:#adb5bd;border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:#adb5bd;border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(0.25rem - 1px);border-bottom-left-radius:calc(0.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:0.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2em;border-bottom-left-radius:.2em}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2em;border-bottom-right-radius:.2em}.badge{display:inline-block;padding:.35em .65em;font-size:0.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{color:#595a5c;background-color:#f8f9fa;border-color:#f5f6f8}.alert-default .alert-link{color:#47484a}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;display:-webkit-flex;height:1rem;overflow:hidden;font-size:0.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{color:#595a5c;background-color:#f8f9fa}.list-group-item-default.list-group-item-action:hover,.list-group-item-default.list-group-item-action:focus{color:#595a5c;background-color:#dfe0e1}.list-group-item-default.list-group-item-action.active{color:#fff;background-color:#595a5c;border-color:#595a5c}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:0.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.toast-header .btn-close{margin-right:-0.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-0.5rem -0.5rem -0.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:1rem}.modal-footer{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(0.3rem - 1px);border-bottom-left-radius:calc(0.3rem - 1px)}.modal-footer>*{margin:.25rem}@media(min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media(min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media(min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:0.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-0.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-0.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-0.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-0.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(0.3rem - 1px);border-top-right-radius:calc(0.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-0.125em;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s;-webkit-animation-duration:1.5s;-moz-animation-duration:1.5s;-ms-animation-duration:1.5s;-o-animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-0.5rem;margin-right:-0.5rem;margin-bottom:-0.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-default{color:#dee2e6}.link-default:hover,.link-default:focus{color:#e5e8eb}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio: calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio: calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-default{border-color:#dee2e6 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.345rem + 1.14vw) !important}.fs-2{font-size:calc(1.3rem + 0.6vw) !important}.fs-3{font-size:calc(1.275rem + 0.3vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:#6c757d !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:transparent !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2em !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#000}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#000}.bg-warning{color:#000}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2.2rem !important}.fs-2{font-size:1.75rem !important}.fs-3{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}.sidebar-item .chapter-number{color:#212529}.quarto-container{min-height:calc(100vh - 132px)}footer.footer .nav-footer,#quarto-header nav{padding-left:1em;padding-right:1em}nav[role=doc-toc]{padding-left:.5em}#quarto-content>*{padding-top:14px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:transform 200ms linear;transition:position 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 85px);min-width:0;display:flex;align-items:center;margin-right:1em}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .sidebar-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{margin-left:0px}.sidebar-tools-main:not(.tools-wide){display:inline-block;vertical-align:middle}.sidebar-tools-main.tools-wide{padding-top:.3em}.sidebar-navigation .sidebar-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em}.sidebar-section{margin-top:.2em;padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 767.98px){.quarto-secondary-nav{display:block}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-secondary-nav .quarto-btn-toggle{color:#545555;padding-right:0}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.quarto-secondary-nav-title{margin-top:.3em;color:#545555;padding-top:4px}div.sidebar-item-container{color:#545555}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(9,79,182,.8)}div.sidebar-item-container.disabled{color:rgba(84,85,85,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:#094fb6}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#f8f9fa}.sidebar.sidebar-navigation:not(.rollup){border-right:1px solid #dee2e6 !important}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{transition:height .15s linear;width:100%}nav.quarto-secondary-nav{background-color:#f8f9fa;border-bottom:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}#quarto-sidebar{width:100%;padding-right:1em;color:#545555}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section a .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-section a.collapsed .bi-chevron-right::before{transform:none}.sidebar-section .bi-chevron-right::before{font-size:.9em;transition:transform 200ms ease}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#0a58ca}.toc-actions{display:flex}.toc-actions p{margin-block-start:0;margin-block-end:0}.toc-actions a{text-decoration:none;color:inherit;font-weight:400}.toc-actions a:hover{color:#0a58ca}.toc-actions .action-links{margin-left:4px}.sidebar nav[role=doc-toc] .toc-actions .bi{margin-left:-4px;font-size:.7rem;color:#6c757d}.sidebar nav[role=doc-toc] .toc-actions .bi:before{padding-top:3px}#quarto-margin-sidebar .toc-actions .bi:before{margin-top:.3rem;font-size:.7rem;color:#6c757d;vertical-align:top}.sidebar nav[role=doc-toc] .toc-actions>div:first-of-type{margin-top:-3px}#quarto-margin-sidebar .toc-actions p,.sidebar nav[role=doc-toc] .toc-actions p{font-size:.875rem}.nav-footer{display:flex;justify-content:center;align-items:center;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{margin-right:auto}.nav-footer-center{min-height:3em;position:absolute;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.nav-footer-right{margin-left:auto}.navbar .quarto-reader-toggle{padding-left:.4em;padding-right:0}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfeff;border-radius:3px}.quarto-reader-toggle.reader.sidebar-tool .quarto-reader-toggle-btn{background-color:#545555;border-radius:3px}.quarto-reader-toggle.sidebar-tool{padding-left:.3em}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.1em;padding-right:.3em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:1rem}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfeff;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfeff;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#212529;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#212529;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#212529;font:inherit;height:calc(1.5em + (0.1rem + 2px));padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#212529;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#212529;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + (0.1rem + 2px))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}#quarto-search-results .aa-Panel{border:solid #ced4da 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#3586fd}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#212529}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e1edff}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#212529}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#ced4da;color:#212529}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:44px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #ced4da}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfeff}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#545555}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(206,212,218,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #ced4da;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#212529;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{border-radius:6px;bottom:inherit;height:auto;margin:0 auto;max-width:850px;position:absolute;top:100px}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(33,37,41,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#212529;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item img.thumbnail-image{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item div.card-img-bg{background-color:#adb5bd;flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post a{color:#212529;display:flex;flex-direction:column;text-decoration:none}div.quarto-post a div.description{flex-shrink:0}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:var(--bs-font-sans-serif);flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:#0d6efd}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:#0d6efd}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:#0d6efd}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:#0d6efd}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:#0d6efd}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;color:#212529;border-radius:.25rem;border:solid 1px #dee2e6;font-size:.875rem}.tippy-box[data-theme~=quarto] .tippy-arrow{color:#dee2e6}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:-1px}.tippy-box[data-placement^=bottom]>.tippy-content{padding:.75em 1em;z-index:1}.top-right{position:absolute;top:1em;right:1em}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:inline-block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p{text-align:left}.quarto-figure-center>figure>p{text-align:center}.quarto-figure-right>figure>p{text-align:right}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link,div[id^=tbl-]>.anchorjs-link{position:absolute;top:0;right:0}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,.table{caption-side:top;margin-bottom:1.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}div.ansi-escaped-output{font-family:monospace;display:block}/*! + */:root,[data-bs-theme=light]{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-black: #000;--bs-white: #ffffff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-gray-100: #f8f9fa;--bs-gray-200: #e9ecef;--bs-gray-300: #dee2e6;--bs-gray-400: #ced4da;--bs-gray-500: #adb5bd;--bs-gray-600: #6c757d;--bs-gray-700: #495057;--bs-gray-800: #343a40;--bs-gray-900: #212529;--bs-default: #dee2e6;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-default-rgb: 222, 226, 230;--bs-primary-rgb: 13, 110, 253;--bs-secondary-rgb: 108, 117, 125;--bs-success-rgb: 25, 135, 84;--bs-info-rgb: 13, 202, 240;--bs-warning-rgb: 255, 193, 7;--bs-danger-rgb: 220, 53, 69;--bs-light-rgb: 248, 249, 250;--bs-dark-rgb: 33, 37, 41;--bs-primary-text-emphasis: #052c65;--bs-secondary-text-emphasis: #2b2f32;--bs-success-text-emphasis: #0a3622;--bs-info-text-emphasis: #055160;--bs-warning-text-emphasis: #664d03;--bs-danger-text-emphasis: #58151c;--bs-light-text-emphasis: #495057;--bs-dark-text-emphasis: #495057;--bs-primary-bg-subtle: #cfe2ff;--bs-secondary-bg-subtle: #e2e3e5;--bs-success-bg-subtle: #d1e7dd;--bs-info-bg-subtle: #cff4fc;--bs-warning-bg-subtle: #fff3cd;--bs-danger-bg-subtle: #f8d7da;--bs-light-bg-subtle: #fcfcfd;--bs-dark-bg-subtle: #ced4da;--bs-primary-border-subtle: #9ec5fe;--bs-secondary-border-subtle: #c4c8cb;--bs-success-border-subtle: #a3cfbb;--bs-info-border-subtle: #9eeaf9;--bs-warning-border-subtle: #ffe69c;--bs-danger-border-subtle: #f1aeb5;--bs-light-border-subtle: #e9ecef;--bs-dark-border-subtle: #adb5bd;--bs-white-rgb: 255, 255, 255;--bs-black-rgb: 0, 0, 0;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-root-font-size: 16px;--bs-body-font-family: Roboto;--bs-body-font-size:1rem;--bs-body-font-weight: 400;--bs-body-line-height: 1.6;--bs-body-color: #212529;--bs-body-color-rgb: 33, 37, 41;--bs-body-bg: #ffffff;--bs-body-bg-rgb: 255, 255, 255;--bs-emphasis-color: #000;--bs-emphasis-color-rgb: 0, 0, 0;--bs-secondary-color: rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb: 33, 37, 41;--bs-secondary-bg: #e9ecef;--bs-secondary-bg-rgb: 233, 236, 239;--bs-tertiary-color: rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb: 33, 37, 41;--bs-tertiary-bg: #f8f9fa;--bs-tertiary-bg-rgb: 248, 249, 250;--bs-heading-color: inherit;--bs-link-color: blue;--bs-link-color-rgb: 0, 0, 255;--bs-link-decoration: underline;--bs-link-hover-color: #0000cc;--bs-link-hover-color-rgb: 0, 0, 204;--bs-code-color: rgb(71, 89, 171);--bs-highlight-bg: #fff3cd;--bs-border-width: 1px;--bs-border-style: solid;--bs-border-color: #dee2e6;--bs-border-color-translucent: rgba(0, 0, 0, 0.175);--bs-border-radius: 0.25rem;--bs-border-radius-sm: 0.2em;--bs-border-radius-lg: 0.5rem;--bs-border-radius-xl: 1rem;--bs-border-radius-xxl: 2rem;--bs-border-radius-2xl: var(--bs-border-radius-xxl);--bs-border-radius-pill: 50rem;--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width: 0.25rem;--bs-focus-ring-opacity: 0.25;--bs-focus-ring-color: rgba(13, 110, 253, 0.25);--bs-form-valid-color: #198754;--bs-form-valid-border-color: #198754;--bs-form-invalid-color: #dc3545;--bs-form-invalid-border-color: #dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color: #dee2e6;--bs-body-color-rgb: 222, 226, 230;--bs-body-bg: #212529;--bs-body-bg-rgb: 33, 37, 41;--bs-emphasis-color: #ffffff;--bs-emphasis-color-rgb: 255, 255, 255;--bs-secondary-color: rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb: 222, 226, 230;--bs-secondary-bg: #343a40;--bs-secondary-bg-rgb: 52, 58, 64;--bs-tertiary-color: rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb: 222, 226, 230;--bs-tertiary-bg: #2b3035;--bs-tertiary-bg-rgb: 43, 48, 53;--bs-primary-text-emphasis: #6ea8fe;--bs-secondary-text-emphasis: #a7acb1;--bs-success-text-emphasis: #75b798;--bs-info-text-emphasis: #6edff6;--bs-warning-text-emphasis: #ffda6a;--bs-danger-text-emphasis: #ea868f;--bs-light-text-emphasis: #f8f9fa;--bs-dark-text-emphasis: #dee2e6;--bs-primary-bg-subtle: #031633;--bs-secondary-bg-subtle: #161719;--bs-success-bg-subtle: #051b11;--bs-info-bg-subtle: #032830;--bs-warning-bg-subtle: #332701;--bs-danger-bg-subtle: #2c0b0e;--bs-light-bg-subtle: #343a40;--bs-dark-bg-subtle: #1a1d20;--bs-primary-border-subtle: #084298;--bs-secondary-border-subtle: #41464b;--bs-success-border-subtle: #0f5132;--bs-info-border-subtle: #087990;--bs-warning-border-subtle: #997404;--bs-danger-border-subtle: #842029;--bs-light-border-subtle: #495057;--bs-dark-border-subtle: #343a40;--bs-heading-color: inherit;--bs-link-color: #6ea8fe;--bs-link-hover-color: #8bb9fe;--bs-link-color-rgb: 110, 168, 254;--bs-link-hover-color-rgb: 139, 185, 254;--bs-code-color: white;--bs-border-color: #495057;--bs-border-color-translucent: rgba(255, 255, 255, 0.15);--bs-form-valid-color: #75b798;--bs-form-valid-border-color: #75b798;--bs-form-invalid-color: #ea868f;--bs-form-invalid-border-color: #ea868f}*,*::before,*::after{box-sizing:border-box}:root{font-size:var(--bs-root-font-size)}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;border:0;border-top:1px solid;opacity:.25}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}h1,.h1{font-size:calc(1.325rem + 0.9vw)}@media(min-width: 1200px){h1,.h1{font-size:2rem}}h2,.h2{font-size:calc(1.29rem + 0.48vw)}@media(min-width: 1200px){h2,.h2{font-size:1.65rem}}h3,.h3{font-size:calc(1.27rem + 0.24vw)}@media(min-width: 1200px){h3,.h3{font-size:1.45rem}}h4,.h4{font-size:1.25rem}h5,.h5{font-size:1.1rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{text-decoration:underline dotted;-webkit-text-decoration:underline dotted;-moz-text-decoration:underline dotted;-ms-text-decoration:underline dotted;-o-text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem;padding:.625rem 1.25rem;border-left:.25rem solid #e9ecef}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}b,strong{font-weight:bolder}small,.small{font-size:0.875em}mark,.mark{padding:.1875em;background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:0.75em;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}a:hover{--bs-link-color-rgb: var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:"Fira Mono";font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:0.875em;color:#000;background-color:rgba(192,192,192,.2);padding:.5rem;border:1px solid var(--bs-border-color, #dee2e6);border-radius:.25rem}pre code{background-color:rgba(0,0,0,0);font-size:inherit;color:inherit;word-break:normal}code{font-size:0.875em;color:var(--bs-code-color);background-color:rgba(192,192,192,.2);border-radius:.25rem;padding:.125rem .25rem;word-wrap:break-word}a>code{color:inherit}kbd{padding:.4rem .4rem;font-size:0.875em;color:#fff;background-color:#212529;border-radius:.2em}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:rgba(33,37,41,.75);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none !important}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + 0.3vw);line-height:inherit}@media(min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media(min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:0.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:0.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:0.875em;color:rgba(33,37,41,.75)}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;width:100%;padding-right:calc(var(--bs-gutter-x)*.5);padding-left:calc(var(--bs-gutter-x)*.5);margin-right:auto;margin-left:auto}@media(min-width: 576px){.container-sm,.container{max-width:540px}}@media(min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media(min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media(min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media(min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}:root{--bs-breakpoint-xs: 0;--bs-breakpoint-sm: 576px;--bs-breakpoint-md: 768px;--bs-breakpoint-lg: 992px;--bs-breakpoint-xl: 1200px;--bs-breakpoint-xxl: 1400px}.grid{display:grid;grid-template-rows:repeat(var(--bs-rows, 1), 1fr);grid-template-columns:repeat(var(--bs-columns, 12), 1fr);gap:var(--bs-gap, 1.5rem)}.grid .g-col-1{grid-column:auto/span 1}.grid .g-col-2{grid-column:auto/span 2}.grid .g-col-3{grid-column:auto/span 3}.grid .g-col-4{grid-column:auto/span 4}.grid .g-col-5{grid-column:auto/span 5}.grid .g-col-6{grid-column:auto/span 6}.grid .g-col-7{grid-column:auto/span 7}.grid .g-col-8{grid-column:auto/span 8}.grid .g-col-9{grid-column:auto/span 9}.grid .g-col-10{grid-column:auto/span 10}.grid .g-col-11{grid-column:auto/span 11}.grid .g-col-12{grid-column:auto/span 12}.grid .g-start-1{grid-column-start:1}.grid .g-start-2{grid-column-start:2}.grid .g-start-3{grid-column-start:3}.grid .g-start-4{grid-column-start:4}.grid .g-start-5{grid-column-start:5}.grid .g-start-6{grid-column-start:6}.grid .g-start-7{grid-column-start:7}.grid .g-start-8{grid-column-start:8}.grid .g-start-9{grid-column-start:9}.grid .g-start-10{grid-column-start:10}.grid .g-start-11{grid-column-start:11}@media(min-width: 576px){.grid .g-col-sm-1{grid-column:auto/span 1}.grid .g-col-sm-2{grid-column:auto/span 2}.grid .g-col-sm-3{grid-column:auto/span 3}.grid .g-col-sm-4{grid-column:auto/span 4}.grid .g-col-sm-5{grid-column:auto/span 5}.grid .g-col-sm-6{grid-column:auto/span 6}.grid .g-col-sm-7{grid-column:auto/span 7}.grid .g-col-sm-8{grid-column:auto/span 8}.grid .g-col-sm-9{grid-column:auto/span 9}.grid .g-col-sm-10{grid-column:auto/span 10}.grid .g-col-sm-11{grid-column:auto/span 11}.grid .g-col-sm-12{grid-column:auto/span 12}.grid .g-start-sm-1{grid-column-start:1}.grid .g-start-sm-2{grid-column-start:2}.grid .g-start-sm-3{grid-column-start:3}.grid .g-start-sm-4{grid-column-start:4}.grid .g-start-sm-5{grid-column-start:5}.grid .g-start-sm-6{grid-column-start:6}.grid .g-start-sm-7{grid-column-start:7}.grid .g-start-sm-8{grid-column-start:8}.grid .g-start-sm-9{grid-column-start:9}.grid .g-start-sm-10{grid-column-start:10}.grid .g-start-sm-11{grid-column-start:11}}@media(min-width: 768px){.grid .g-col-md-1{grid-column:auto/span 1}.grid .g-col-md-2{grid-column:auto/span 2}.grid .g-col-md-3{grid-column:auto/span 3}.grid .g-col-md-4{grid-column:auto/span 4}.grid .g-col-md-5{grid-column:auto/span 5}.grid .g-col-md-6{grid-column:auto/span 6}.grid .g-col-md-7{grid-column:auto/span 7}.grid .g-col-md-8{grid-column:auto/span 8}.grid .g-col-md-9{grid-column:auto/span 9}.grid .g-col-md-10{grid-column:auto/span 10}.grid .g-col-md-11{grid-column:auto/span 11}.grid .g-col-md-12{grid-column:auto/span 12}.grid .g-start-md-1{grid-column-start:1}.grid .g-start-md-2{grid-column-start:2}.grid .g-start-md-3{grid-column-start:3}.grid .g-start-md-4{grid-column-start:4}.grid .g-start-md-5{grid-column-start:5}.grid .g-start-md-6{grid-column-start:6}.grid .g-start-md-7{grid-column-start:7}.grid .g-start-md-8{grid-column-start:8}.grid .g-start-md-9{grid-column-start:9}.grid .g-start-md-10{grid-column-start:10}.grid .g-start-md-11{grid-column-start:11}}@media(min-width: 992px){.grid .g-col-lg-1{grid-column:auto/span 1}.grid .g-col-lg-2{grid-column:auto/span 2}.grid .g-col-lg-3{grid-column:auto/span 3}.grid .g-col-lg-4{grid-column:auto/span 4}.grid .g-col-lg-5{grid-column:auto/span 5}.grid .g-col-lg-6{grid-column:auto/span 6}.grid .g-col-lg-7{grid-column:auto/span 7}.grid .g-col-lg-8{grid-column:auto/span 8}.grid .g-col-lg-9{grid-column:auto/span 9}.grid .g-col-lg-10{grid-column:auto/span 10}.grid .g-col-lg-11{grid-column:auto/span 11}.grid .g-col-lg-12{grid-column:auto/span 12}.grid .g-start-lg-1{grid-column-start:1}.grid .g-start-lg-2{grid-column-start:2}.grid .g-start-lg-3{grid-column-start:3}.grid .g-start-lg-4{grid-column-start:4}.grid .g-start-lg-5{grid-column-start:5}.grid .g-start-lg-6{grid-column-start:6}.grid .g-start-lg-7{grid-column-start:7}.grid .g-start-lg-8{grid-column-start:8}.grid .g-start-lg-9{grid-column-start:9}.grid .g-start-lg-10{grid-column-start:10}.grid .g-start-lg-11{grid-column-start:11}}@media(min-width: 1200px){.grid .g-col-xl-1{grid-column:auto/span 1}.grid .g-col-xl-2{grid-column:auto/span 2}.grid .g-col-xl-3{grid-column:auto/span 3}.grid .g-col-xl-4{grid-column:auto/span 4}.grid .g-col-xl-5{grid-column:auto/span 5}.grid .g-col-xl-6{grid-column:auto/span 6}.grid .g-col-xl-7{grid-column:auto/span 7}.grid .g-col-xl-8{grid-column:auto/span 8}.grid .g-col-xl-9{grid-column:auto/span 9}.grid .g-col-xl-10{grid-column:auto/span 10}.grid .g-col-xl-11{grid-column:auto/span 11}.grid .g-col-xl-12{grid-column:auto/span 12}.grid .g-start-xl-1{grid-column-start:1}.grid .g-start-xl-2{grid-column-start:2}.grid .g-start-xl-3{grid-column-start:3}.grid .g-start-xl-4{grid-column-start:4}.grid .g-start-xl-5{grid-column-start:5}.grid .g-start-xl-6{grid-column-start:6}.grid .g-start-xl-7{grid-column-start:7}.grid .g-start-xl-8{grid-column-start:8}.grid .g-start-xl-9{grid-column-start:9}.grid .g-start-xl-10{grid-column-start:10}.grid .g-start-xl-11{grid-column-start:11}}@media(min-width: 1400px){.grid .g-col-xxl-1{grid-column:auto/span 1}.grid .g-col-xxl-2{grid-column:auto/span 2}.grid .g-col-xxl-3{grid-column:auto/span 3}.grid .g-col-xxl-4{grid-column:auto/span 4}.grid .g-col-xxl-5{grid-column:auto/span 5}.grid .g-col-xxl-6{grid-column:auto/span 6}.grid .g-col-xxl-7{grid-column:auto/span 7}.grid .g-col-xxl-8{grid-column:auto/span 8}.grid .g-col-xxl-9{grid-column:auto/span 9}.grid .g-col-xxl-10{grid-column:auto/span 10}.grid .g-col-xxl-11{grid-column:auto/span 11}.grid .g-col-xxl-12{grid-column:auto/span 12}.grid .g-start-xxl-1{grid-column-start:1}.grid .g-start-xxl-2{grid-column-start:2}.grid .g-start-xxl-3{grid-column-start:3}.grid .g-start-xxl-4{grid-column-start:4}.grid .g-start-xxl-5{grid-column-start:5}.grid .g-start-xxl-6{grid-column-start:6}.grid .g-start-xxl-7{grid-column-start:7}.grid .g-start-xxl-8{grid-column-start:8}.grid .g-start-xxl-9{grid-column-start:9}.grid .g-start-xxl-10{grid-column-start:10}.grid .g-start-xxl-11{grid-column-start:11}}.table{--bs-table-color-type: initial;--bs-table-bg-type: initial;--bs-table-color-state: initial;--bs-table-bg-state: initial;--bs-table-color: #212529;--bs-table-bg: #ffffff;--bs-table-border-color: #dee2e6;--bs-table-accent-bg: transparent;--bs-table-striped-color: #212529;--bs-table-striped-bg: rgba(0, 0, 0, 0.05);--bs-table-active-color: #212529;--bs-table-active-bg: rgba(0, 0, 0, 0.1);--bs-table-hover-color: #212529;--bs-table-hover-bg: rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;vertical-align:top;border-color:var(--bs-table-border-color)}.table>:not(caption)>*>*{padding:.5rem .5rem;color:var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg)))}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table-group-divider{border-top:calc(1px*2) solid #9ba5ae}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-striped-columns>:not(caption)>tr>:nth-child(even){--bs-table-color-type: var(--bs-table-striped-color);--bs-table-bg-type: var(--bs-table-striped-bg)}.table-active{--bs-table-color-state: var(--bs-table-active-color);--bs-table-bg-state: var(--bs-table-active-bg)}.table-hover>tbody>tr:hover>*{--bs-table-color-state: var(--bs-table-hover-color);--bs-table-bg-state: var(--bs-table-hover-bg)}.table-primary{--bs-table-color: #000;--bs-table-bg: #cfe2ff;--bs-table-border-color: #bacbe6;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-secondary{--bs-table-color: #000;--bs-table-bg: #e2e3e5;--bs-table-border-color: #cbccce;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-success{--bs-table-color: #000;--bs-table-bg: #d1e7dd;--bs-table-border-color: #bcd0c7;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-info{--bs-table-color: #000;--bs-table-bg: #cff4fc;--bs-table-border-color: #badce3;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-warning{--bs-table-color: #000;--bs-table-bg: #fff3cd;--bs-table-border-color: #e6dbb9;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-danger{--bs-table-color: #000;--bs-table-bg: #f8d7da;--bs-table-border-color: #dfc2c4;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-light{--bs-table-color: #000;--bs-table-bg: #f8f9fa;--bs-table-border-color: #dfe0e1;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-dark{--bs-table-color: #ffffff;--bs-table-bg: #212529;--bs-table-border-color: #373b3e;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #ffffff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #ffffff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #ffffff;color:var(--bs-table-color);border-color:var(--bs-table-border-color)}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media(max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media(max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label,.shiny-input-container .control-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(0.375rem + 1px);padding-bottom:calc(0.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(0.5rem + 1px);padding-bottom:calc(0.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(0.25rem + 1px);padding-bottom:calc(0.25rem + 1px);font-size:0.875rem}.form-text{margin-top:.25rem;font-size:0.875em;color:rgba(33,37,41,.75)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-clip:padding-box;border:1px solid #dee2e6;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{min-width:85px;height:1.5em;margin:0}.form-control::-webkit-datetime-edit{display:block;padding:0}.form-control::placeholder{color:rgba(33,37,41,.75);opacity:1}.form-control:disabled{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-0.375rem -0.75rem;margin-inline-end:.75rem;color:#212529;background-color:#f8f9fa;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#e9ecef}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext:focus{outline:0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2));padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-0.25rem -0.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2));padding:.5rem 1rem;font-size:1.25rem;border-radius:.5rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-0.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + 0.75rem + calc(1px * 2))}textarea.form-control-sm{min-height:calc(1.5em + 0.5rem + calc(1px * 2))}textarea.form-control-lg{min-height:calc(1.5em + 1rem + calc(1px * 2))}.form-control-color{width:3rem;height:calc(1.5em + 0.75rem + calc(1px * 2));padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{border:0 !important;border-radius:.25rem}.form-control-color::-webkit-color-swatch{border:0 !important;border-radius:.25rem}.form-control-color.form-control-sm{height:calc(1.5em + 0.5rem + calc(1px * 2))}.form-control-color.form-control-lg{height:calc(1.5em + 1rem + calc(1px * 2))}.form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#fff;background-image:var(--bs-form-select-bg-img),var(--bs-form-select-bg-icon, none);background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #dee2e6;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:0.875rem;border-radius:.2em}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.5rem}[data-bs-theme=dark] .form-select{--bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e")}.form-check,.shiny-input-container .checkbox,.shiny-input-container .radio{display:block;min-height:1.5rem;padding-left:0;margin-bottom:.125rem}.form-check .form-check-input,.form-check .shiny-input-container .checkbox input,.form-check .shiny-input-container .radio input,.shiny-input-container .checkbox .form-check-input,.shiny-input-container .checkbox .shiny-input-container .checkbox input,.shiny-input-container .checkbox .shiny-input-container .radio input,.shiny-input-container .radio .form-check-input,.shiny-input-container .radio .shiny-input-container .checkbox input,.shiny-input-container .radio .shiny-input-container .radio input{float:left;margin-left:0}.form-check-reverse{padding-right:0;padding-left:0;text-align:right}.form-check-reverse .form-check-input{float:right;margin-right:0;margin-left:0}.form-check-input,.shiny-input-container .checkbox input,.shiny-input-container .checkbox-inline input,.shiny-input-container .radio input,.shiny-input-container .radio-inline input{--bs-form-check-bg: #ffffff;width:1em;height:1em;margin-top:.3em;vertical-align:top;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:var(--bs-form-check-bg);background-image:var(--bs-form-check-bg-image);background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid #dee2e6;print-color-adjust:exact}.form-check-input[type=checkbox],.shiny-input-container .checkbox input[type=checkbox],.shiny-input-container .checkbox-inline input[type=checkbox],.shiny-input-container .radio input[type=checkbox],.shiny-input-container .radio-inline input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio],.shiny-input-container .checkbox input[type=radio],.shiny-input-container .checkbox-inline input[type=radio],.shiny-input-container .radio input[type=radio],.shiny-input-container .radio-inline input[type=radio]{border-radius:50%}.form-check-input:active,.shiny-input-container .checkbox input:active,.shiny-input-container .checkbox-inline input:active,.shiny-input-container .radio input:active,.shiny-input-container .radio-inline input:active{filter:brightness(90%)}.form-check-input:focus,.shiny-input-container .checkbox input:focus,.shiny-input-container .checkbox-inline input:focus,.shiny-input-container .radio input:focus,.shiny-input-container .radio-inline input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked,.shiny-input-container .checkbox input:checked,.shiny-input-container .checkbox-inline input:checked,.shiny-input-container .radio input:checked,.shiny-input-container .radio-inline input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox],.shiny-input-container .checkbox input:checked[type=checkbox],.shiny-input-container .checkbox-inline input:checked[type=checkbox],.shiny-input-container .radio input:checked[type=checkbox],.shiny-input-container .radio-inline input:checked[type=checkbox]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio],.shiny-input-container .checkbox input:checked[type=radio],.shiny-input-container .checkbox-inline input:checked[type=radio],.shiny-input-container .radio input:checked[type=radio],.shiny-input-container .radio-inline input:checked[type=radio]{--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23ffffff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate,.shiny-input-container .checkbox input[type=checkbox]:indeterminate,.shiny-input-container .checkbox-inline input[type=checkbox]:indeterminate,.shiny-input-container .radio input[type=checkbox]:indeterminate,.shiny-input-container .radio-inline input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;--bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled,.shiny-input-container .checkbox input:disabled,.shiny-input-container .checkbox-inline input:disabled,.shiny-input-container .radio input:disabled,.shiny-input-container .radio-inline input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input[disabled]~span,.form-check-input:disabled~.form-check-label,.form-check-input:disabled~span,.shiny-input-container .checkbox input[disabled]~.form-check-label,.shiny-input-container .checkbox input[disabled]~span,.shiny-input-container .checkbox input:disabled~.form-check-label,.shiny-input-container .checkbox input:disabled~span,.shiny-input-container .checkbox-inline input[disabled]~.form-check-label,.shiny-input-container .checkbox-inline input[disabled]~span,.shiny-input-container .checkbox-inline input:disabled~.form-check-label,.shiny-input-container .checkbox-inline input:disabled~span,.shiny-input-container .radio input[disabled]~.form-check-label,.shiny-input-container .radio input[disabled]~span,.shiny-input-container .radio input:disabled~.form-check-label,.shiny-input-container .radio input:disabled~span,.shiny-input-container .radio-inline input[disabled]~.form-check-label,.shiny-input-container .radio-inline input[disabled]~span,.shiny-input-container .radio-inline input:disabled~.form-check-label,.shiny-input-container .radio-inline input:disabled~span{cursor:default;opacity:.5}.form-check-label,.shiny-input-container .checkbox label,.shiny-input-container .checkbox-inline label,.shiny-input-container .radio label,.shiny-input-container .radio-inline label{cursor:pointer}.form-switch{padding-left:2.5em}.form-switch .form-check-input{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");width:2em;margin-left:-2.5em;background-image:var(--bs-form-switch-bg);background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffffff'/%3e%3c/svg%3e")}.form-switch.form-check-reverse{padding-right:2.5em;padding-left:0}.form-switch.form-check-reverse .form-check-input{margin-right:-2.5em;margin-left:0}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus){--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e")}.form-range{width:100%;height:1.5rem;padding:0;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:rgba(0,0,0,0)}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-0.25rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;-o-appearance:none;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#f8f9fa;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:rgba(33,37,41,.75)}.form-range:disabled::-moz-range-thumb{background-color:rgba(33,37,41,.75)}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-control-plaintext,.form-floating>.form-select{height:calc(3.5rem + calc(1px * 2));min-height:calc(3.5rem + calc(1px * 2));line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;z-index:2;height:100%;padding:1rem .75rem;overflow:hidden;text-align:start;text-overflow:ellipsis;white-space:nowrap;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media(prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control,.form-floating>.form-control-plaintext{padding:1rem .75rem}.form-floating>.form-control::placeholder,.form-floating>.form-control-plaintext::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown),.form-floating>.form-control-plaintext:focus,.form-floating>.form-control-plaintext:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.form-floating>.form-control-plaintext:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-control-plaintext~label,.form-floating>.form-select~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control:focus~label::after,.form-floating>.form-control:not(:placeholder-shown)~label::after,.form-floating>.form-control-plaintext~label::after,.form-floating>.form-select~label::after{position:absolute;inset:1rem .375rem;z-index:-1;height:1.5em;content:"";background-color:#fff;border-radius:.25rem}.form-floating>.form-control:-webkit-autofill~label{color:rgba(var(--bs-body-color-rgb), 0.65);transform:scale(0.85) translateY(-0.5rem) translateX(0.15rem)}.form-floating>.form-control-plaintext~label{border-width:1px 0}.form-floating>:disabled~label,.form-floating>.form-control:disabled~label{color:#6c757d}.form-floating>:disabled~label::after,.form-floating>.form-control:disabled~label::after{background-color:#e9ecef}.input-group{position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:stretch;-webkit-align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select,.input-group>.form-floating{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus,.input-group>.form-floating:focus-within{z-index:5}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:5}.input-group-text{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#f8f9fa;border:1px solid #dee2e6;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.5rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:0.875rem;border-radius:.2em}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-control,.input-group:not(.has-validation)>.form-floating:not(:last-child)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-control,.input-group.has-validation>.form-floating:nth-last-child(n+3)>.form-select{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:calc(1px*-1);border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.form-floating:not(:first-child)>.form-control,.input-group>.form-floating:not(:first-child)>.form-select{border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#198754;border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#198754;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754}.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"],.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-control-color:valid,.form-control-color.is-valid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):valid,.input-group>.form-control:not(:focus).is-valid,.was-validated .input-group>.form-select:not(:focus):valid,.input-group>.form-select:not(:focus).is-valid,.was-validated .input-group>.form-floating:not(:focus-within):valid,.input-group>.form-floating:not(:focus-within).is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:0.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:0.875rem;color:#fff;background-color:#dc3545;border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + 0.75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(0.375em + 0.1875rem) center;background-size:calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + 0.75rem);background-position:top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545}.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"],.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"]{--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");padding-right:4.125rem;background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(0.75em + 0.375rem) calc(0.75em + 0.375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-control-color:invalid,.form-control-color.is-invalid{width:calc(3rem + calc(1.5em + 0.75rem))}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group>.form-control:not(:focus):invalid,.input-group>.form-control:not(:focus).is-invalid,.was-validated .input-group>.form-select:not(:focus):invalid,.input-group>.form-select:not(:focus).is-invalid,.was-validated .input-group>.form-floating:not(:focus-within):invalid,.input-group>.form-floating:not(:focus-within).is-invalid{z-index:4}.btn{--bs-btn-padding-x: 0.75rem;--bs-btn-padding-y: 0.375rem;--bs-btn-font-family: ;--bs-btn-font-size:1rem;--bs-btn-font-weight: 400;--bs-btn-line-height: 1.5;--bs-btn-color: #212529;--bs-btn-bg: transparent;--bs-btn-border-width: 1px;--bs-btn-border-color: transparent;--bs-btn-border-radius: 0.25rem;--bs-btn-hover-border-color: transparent;--bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);--bs-btn-disabled-opacity: 0.65;--bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);display:inline-block;padding:var(--bs-btn-padding-y) var(--bs-btn-padding-x);font-family:var(--bs-btn-font-family);font-size:var(--bs-btn-font-size);font-weight:var(--bs-btn-font-weight);line-height:var(--bs-btn-line-height);color:var(--bs-btn-color);text-align:center;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;border:var(--bs-btn-border-width) solid var(--bs-btn-border-color);border-radius:var(--bs-btn-border-radius);background-color:var(--bs-btn-bg);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color)}.btn-check+.btn:hover{color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border-color:var(--bs-btn-border-color)}.btn:focus-visible{color:var(--bs-btn-hover-color);background-color:var(--bs-btn-hover-bg);border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:focus-visible+.btn{border-color:var(--bs-btn-hover-border-color);outline:0;box-shadow:var(--bs-btn-focus-box-shadow)}.btn-check:checked+.btn,:not(.btn-check)+.btn:active,.btn:first-child:active,.btn.active,.btn.show{color:var(--bs-btn-active-color);background-color:var(--bs-btn-active-bg);border-color:var(--bs-btn-active-border-color)}.btn-check:checked+.btn:focus-visible,:not(.btn-check)+.btn:active:focus-visible,.btn:first-child:active:focus-visible,.btn.active:focus-visible,.btn.show:focus-visible{box-shadow:var(--bs-btn-focus-box-shadow)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{color:var(--bs-btn-disabled-color);pointer-events:none;background-color:var(--bs-btn-disabled-bg);border-color:var(--bs-btn-disabled-border-color);opacity:var(--bs-btn-disabled-opacity)}.btn-default{--bs-btn-color: #000;--bs-btn-bg: #dee2e6;--bs-btn-border-color: #dee2e6;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #e3e6ea;--bs-btn-hover-border-color: #e1e5e9;--bs-btn-focus-shadow-rgb: 189, 192, 196;--bs-btn-active-color: #000;--bs-btn-active-bg: #e5e8eb;--bs-btn-active-border-color: #e1e5e9;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #dee2e6;--bs-btn-disabled-border-color: #dee2e6}.btn-primary{--bs-btn-color: #ffffff;--bs-btn-bg: #0d6efd;--bs-btn-border-color: #0d6efd;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #0b5ed7;--bs-btn-hover-border-color: #0a58ca;--bs-btn-focus-shadow-rgb: 49, 132, 253;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #0a58ca;--bs-btn-active-border-color: #0a53be;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #0d6efd;--bs-btn-disabled-border-color: #0d6efd}.btn-secondary{--bs-btn-color: #ffffff;--bs-btn-bg: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #5c636a;--bs-btn-hover-border-color: #565e64;--bs-btn-focus-shadow-rgb: 130, 138, 145;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #565e64;--bs-btn-active-border-color: #51585e;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #6c757d;--bs-btn-disabled-border-color: #6c757d}.btn-success{--bs-btn-color: #ffffff;--bs-btn-bg: #198754;--bs-btn-border-color: #198754;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #157347;--bs-btn-hover-border-color: #146c43;--bs-btn-focus-shadow-rgb: 60, 153, 110;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #146c43;--bs-btn-active-border-color: #13653f;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #198754;--bs-btn-disabled-border-color: #198754}.btn-info{--bs-btn-color: #000;--bs-btn-bg: #0dcaf0;--bs-btn-border-color: #0dcaf0;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #31d2f2;--bs-btn-hover-border-color: #25cff2;--bs-btn-focus-shadow-rgb: 11, 172, 204;--bs-btn-active-color: #000;--bs-btn-active-bg: #3dd5f3;--bs-btn-active-border-color: #25cff2;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #0dcaf0;--bs-btn-disabled-border-color: #0dcaf0}.btn-warning{--bs-btn-color: #000;--bs-btn-bg: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffca2c;--bs-btn-hover-border-color: #ffc720;--bs-btn-focus-shadow-rgb: 217, 164, 6;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffcd39;--bs-btn-active-border-color: #ffc720;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #ffc107;--bs-btn-disabled-border-color: #ffc107}.btn-danger{--bs-btn-color: #ffffff;--bs-btn-bg: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #bb2d3b;--bs-btn-hover-border-color: #b02a37;--bs-btn-focus-shadow-rgb: 225, 83, 97;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #b02a37;--bs-btn-active-border-color: #a52834;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #dc3545;--bs-btn-disabled-border-color: #dc3545}.btn-light{--bs-btn-color: #000;--bs-btn-bg: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #d3d4d5;--bs-btn-hover-border-color: #c6c7c8;--bs-btn-focus-shadow-rgb: 211, 212, 213;--bs-btn-active-color: #000;--bs-btn-active-bg: #c6c7c8;--bs-btn-active-border-color: #babbbc;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #000;--bs-btn-disabled-bg: #f8f9fa;--bs-btn-disabled-border-color: #f8f9fa}.btn-dark{--bs-btn-color: #ffffff;--bs-btn-bg: #212529;--bs-btn-border-color: #212529;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #424649;--bs-btn-hover-border-color: #373b3e;--bs-btn-focus-shadow-rgb: 66, 70, 73;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #4d5154;--bs-btn-active-border-color: #373b3e;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #212529;--bs-btn-disabled-border-color: #212529}.btn-outline-default{--bs-btn-color: #dee2e6;--bs-btn-border-color: #dee2e6;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #dee2e6;--bs-btn-hover-border-color: #dee2e6;--bs-btn-focus-shadow-rgb: 222, 226, 230;--bs-btn-active-color: #000;--bs-btn-active-bg: #dee2e6;--bs-btn-active-border-color: #dee2e6;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #dee2e6;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #dee2e6;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-primary{--bs-btn-color: #0d6efd;--bs-btn-border-color: #0d6efd;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #0d6efd;--bs-btn-hover-border-color: #0d6efd;--bs-btn-focus-shadow-rgb: 13, 110, 253;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #0d6efd;--bs-btn-active-border-color: #0d6efd;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #0d6efd;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #0d6efd;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-secondary{--bs-btn-color: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #6c757d;--bs-btn-hover-border-color: #6c757d;--bs-btn-focus-shadow-rgb: 108, 117, 125;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #6c757d;--bs-btn-active-border-color: #6c757d;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #6c757d;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-success{--bs-btn-color: #198754;--bs-btn-border-color: #198754;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #198754;--bs-btn-hover-border-color: #198754;--bs-btn-focus-shadow-rgb: 25, 135, 84;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #198754;--bs-btn-active-border-color: #198754;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #198754;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #198754;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-info{--bs-btn-color: #0dcaf0;--bs-btn-border-color: #0dcaf0;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #0dcaf0;--bs-btn-hover-border-color: #0dcaf0;--bs-btn-focus-shadow-rgb: 13, 202, 240;--bs-btn-active-color: #000;--bs-btn-active-bg: #0dcaf0;--bs-btn-active-border-color: #0dcaf0;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #0dcaf0;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #0dcaf0;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-warning{--bs-btn-color: #ffc107;--bs-btn-border-color: #ffc107;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #ffc107;--bs-btn-hover-border-color: #ffc107;--bs-btn-focus-shadow-rgb: 255, 193, 7;--bs-btn-active-color: #000;--bs-btn-active-bg: #ffc107;--bs-btn-active-border-color: #ffc107;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffc107;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #ffc107;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-danger{--bs-btn-color: #dc3545;--bs-btn-border-color: #dc3545;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #dc3545;--bs-btn-hover-border-color: #dc3545;--bs-btn-focus-shadow-rgb: 220, 53, 69;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #dc3545;--bs-btn-active-border-color: #dc3545;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #dc3545;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #dc3545;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-light{--bs-btn-color: #f8f9fa;--bs-btn-border-color: #f8f9fa;--bs-btn-hover-color: #000;--bs-btn-hover-bg: #f8f9fa;--bs-btn-hover-border-color: #f8f9fa;--bs-btn-focus-shadow-rgb: 248, 249, 250;--bs-btn-active-color: #000;--bs-btn-active-bg: #f8f9fa;--bs-btn-active-border-color: #f8f9fa;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #f8f9fa;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #f8f9fa;--bs-btn-bg: transparent;--bs-gradient: none}.btn-outline-dark{--bs-btn-color: #212529;--bs-btn-border-color: #212529;--bs-btn-hover-color: #ffffff;--bs-btn-hover-bg: #212529;--bs-btn-hover-border-color: #212529;--bs-btn-focus-shadow-rgb: 33, 37, 41;--bs-btn-active-color: #ffffff;--bs-btn-active-bg: #212529;--bs-btn-active-border-color: #212529;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #212529;--bs-btn-disabled-bg: transparent;--bs-btn-disabled-border-color: #212529;--bs-btn-bg: transparent;--bs-gradient: none}.btn-link{--bs-btn-font-weight: 400;--bs-btn-color: blue;--bs-btn-bg: transparent;--bs-btn-border-color: transparent;--bs-btn-hover-color: #0000cc;--bs-btn-hover-border-color: transparent;--bs-btn-active-color: #0000cc;--bs-btn-active-border-color: transparent;--bs-btn-disabled-color: #6c757d;--bs-btn-disabled-border-color: transparent;--bs-btn-box-shadow: 0 0 0 #000;--bs-btn-focus-shadow-rgb: 38, 38, 255;text-decoration:underline;-webkit-text-decoration:underline;-moz-text-decoration:underline;-ms-text-decoration:underline;-o-text-decoration:underline}.btn-link:focus-visible{color:var(--bs-btn-color)}.btn-link:hover{color:var(--bs-btn-hover-color)}.btn-lg,.btn-group-lg>.btn{--bs-btn-padding-y: 0.5rem;--bs-btn-padding-x: 1rem;--bs-btn-font-size:1.25rem;--bs-btn-border-radius: 0.5rem}.btn-sm,.btn-group-sm>.btn{--bs-btn-padding-y: 0.25rem;--bs-btn-padding-x: 0.5rem;--bs-btn-font-size:0.875rem;--bs-btn-border-radius: 0.2em}.fade{transition:opacity .15s linear}@media(prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .2s ease}@media(prefers-reduced-motion: reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media(prefers-reduced-motion: reduce){.collapsing.collapse-horizontal{transition:none}}.dropup,.dropend,.dropdown,.dropstart,.dropup-center,.dropdown-center{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{--bs-dropdown-zindex: 1000;--bs-dropdown-min-width: 10rem;--bs-dropdown-padding-x: 0;--bs-dropdown-padding-y: 0.5rem;--bs-dropdown-spacer: 0.125rem;--bs-dropdown-font-size:1rem;--bs-dropdown-color: #212529;--bs-dropdown-bg: #ffffff;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-border-radius: 0.25rem;--bs-dropdown-border-width: 1px;--bs-dropdown-inner-border-radius: calc(0.25rem - 1px);--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-divider-margin-y: 0.5rem;--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-dropdown-link-color: #212529;--bs-dropdown-link-hover-color: #212529;--bs-dropdown-link-hover-bg: #f8f9fa;--bs-dropdown-link-active-color: #ffffff;--bs-dropdown-link-active-bg: #0d6efd;--bs-dropdown-link-disabled-color: rgba(33, 37, 41, 0.5);--bs-dropdown-item-padding-x: 1rem;--bs-dropdown-item-padding-y: 0.25rem;--bs-dropdown-header-color: #6c757d;--bs-dropdown-header-padding-x: 1rem;--bs-dropdown-header-padding-y: 0.5rem;position:absolute;z-index:var(--bs-dropdown-zindex);display:none;min-width:var(--bs-dropdown-min-width);padding:var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);margin:0;font-size:var(--bs-dropdown-font-size);color:var(--bs-dropdown-color);text-align:left;list-style:none;background-color:var(--bs-dropdown-bg);background-clip:padding-box;border:var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);border-radius:var(--bs-dropdown-border-radius)}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:var(--bs-dropdown-spacer)}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media(min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media(min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:var(--bs-dropdown-spacer)}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:var(--bs-dropdown-spacer)}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:var(--bs-dropdown-spacer)}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:var(--bs-dropdown-divider-margin-y) 0;overflow:hidden;border-top:1px solid var(--bs-dropdown-divider-bg);opacity:1}.dropdown-item{display:block;width:100%;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);clear:both;font-weight:400;color:var(--bs-dropdown-link-color);text-align:inherit;text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0;border-radius:var(--bs-dropdown-item-border-radius, 0)}.dropdown-item:hover,.dropdown-item:focus{color:var(--bs-dropdown-link-hover-color);background-color:var(--bs-dropdown-link-hover-bg)}.dropdown-item.active,.dropdown-item:active{color:var(--bs-dropdown-link-active-color);text-decoration:none;background-color:var(--bs-dropdown-link-active-bg)}.dropdown-item.disabled,.dropdown-item:disabled{color:var(--bs-dropdown-link-disabled-color);pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);margin-bottom:0;font-size:0.875rem;color:var(--bs-dropdown-header-color);white-space:nowrap}.dropdown-item-text{display:block;padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);color:var(--bs-dropdown-link-color)}.dropdown-menu-dark{--bs-dropdown-color: #dee2e6;--bs-dropdown-bg: #343a40;--bs-dropdown-border-color: rgba(0, 0, 0, 0.175);--bs-dropdown-box-shadow: ;--bs-dropdown-link-color: #dee2e6;--bs-dropdown-link-hover-color: #ffffff;--bs-dropdown-divider-bg: rgba(0, 0, 0, 0.175);--bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);--bs-dropdown-link-active-color: #ffffff;--bs-dropdown-link-active-bg: #0d6efd;--bs-dropdown-link-disabled-color: #adb5bd;--bs-dropdown-header-color: #adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;justify-content:flex-start;-webkit-justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group{border-radius:.25rem}.btn-group>:not(.btn-check:first-child)+.btn,.btn-group>.btn-group:not(:first-child){margin-left:calc(1px*-1)}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn.dropdown-toggle-split:first-child,.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;-webkit-flex-direction:column;align-items:flex-start;-webkit-align-items:flex-start;justify-content:center;-webkit-justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:calc(1px*-1)}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{--bs-nav-link-padding-x: 1rem;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: blue;--bs-nav-link-hover-color: #0000cc;--bs-nav-link-disabled-color: rgba(33, 37, 41, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);font-size:var(--bs-nav-link-font-size);font-weight:var(--bs-nav-link-font-weight);color:var(--bs-nav-link-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background:none;border:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media(prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link:hover,.nav-link:focus{color:var(--bs-nav-link-hover-color)}.nav-link:focus-visible{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.nav-link.disabled,.nav-link:disabled{color:var(--bs-nav-link-disabled-color);pointer-events:none;cursor:default}.nav-tabs{--bs-nav-tabs-border-width: 1px;--bs-nav-tabs-border-color: #dee2e6;--bs-nav-tabs-border-radius: 0.25rem;--bs-nav-tabs-link-hover-border-color: #e9ecef #e9ecef #dee2e6;--bs-nav-tabs-link-active-color: #000;--bs-nav-tabs-link-active-bg: #ffffff;--bs-nav-tabs-link-active-border-color: #dee2e6 #dee2e6 #ffffff;border-bottom:var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color)}.nav-tabs .nav-link{margin-bottom:calc(-1*var(--bs-nav-tabs-border-width));border:var(--bs-nav-tabs-border-width) solid rgba(0,0,0,0);border-top-left-radius:var(--bs-nav-tabs-border-radius);border-top-right-radius:var(--bs-nav-tabs-border-radius)}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{isolation:isolate;border-color:var(--bs-nav-tabs-link-hover-border-color)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:var(--bs-nav-tabs-link-active-color);background-color:var(--bs-nav-tabs-link-active-bg);border-color:var(--bs-nav-tabs-link-active-border-color)}.nav-tabs .dropdown-menu{margin-top:calc(-1*var(--bs-nav-tabs-border-width));border-top-left-radius:0;border-top-right-radius:0}.nav-pills{--bs-nav-pills-border-radius: 0.25rem;--bs-nav-pills-link-active-color: #ffffff;--bs-nav-pills-link-active-bg: #0d6efd}.nav-pills .nav-link{border-radius:var(--bs-nav-pills-border-radius)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:var(--bs-nav-pills-link-active-color);background-color:var(--bs-nav-pills-link-active-bg)}.nav-underline{--bs-nav-underline-gap: 1rem;--bs-nav-underline-border-width: 0.125rem;--bs-nav-underline-link-active-color: #000;gap:var(--bs-nav-underline-gap)}.nav-underline .nav-link{padding-right:0;padding-left:0;border-bottom:var(--bs-nav-underline-border-width) solid rgba(0,0,0,0)}.nav-underline .nav-link:hover,.nav-underline .nav-link:focus{border-bottom-color:currentcolor}.nav-underline .nav-link.active,.nav-underline .show>.nav-link{font-weight:700;color:var(--bs-nav-underline-link-active-color);border-bottom-color:currentcolor}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;-webkit-flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;-webkit-flex-basis:0;flex-grow:1;-webkit-flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{--bs-navbar-padding-x: 0;--bs-navbar-padding-y: 0.5rem;--bs-navbar-color: #fdfefe;--bs-navbar-hover-color: rgba(252, 252, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fcfcff;--bs-navbar-brand-padding-y: 0.3125rem;--bs-navbar-brand-margin-end: 1rem;--bs-navbar-brand-font-size: 1.25rem;--bs-navbar-brand-color: #fdfefe;--bs-navbar-brand-hover-color: #fcfcff;--bs-navbar-nav-link-padding-x: 0.5rem;--bs-navbar-toggler-padding-y: 0.25;--bs-navbar-toggler-padding-x: 0;--bs-navbar-toggler-font-size: 1.25rem;--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfefe' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");--bs-navbar-toggler-border-color: rgba(253, 254, 254, 0);--bs-navbar-toggler-border-radius: 0.25rem;--bs-navbar-toggler-focus-width: 0.25rem;--bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;position:relative;display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-navbar-padding-y) var(--bs-navbar-padding-x)}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;display:-webkit-flex;flex-wrap:inherit;-webkit-flex-wrap:inherit;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between}.navbar-brand{padding-top:var(--bs-navbar-brand-padding-y);padding-bottom:var(--bs-navbar-brand-padding-y);margin-right:var(--bs-navbar-brand-margin-end);font-size:var(--bs-navbar-brand-font-size);color:var(--bs-navbar-brand-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;white-space:nowrap}.navbar-brand:hover,.navbar-brand:focus{color:var(--bs-navbar-brand-hover-color)}.navbar-nav{--bs-nav-link-padding-x: 0;--bs-nav-link-padding-y: 0.5rem;--bs-nav-link-font-weight: ;--bs-nav-link-color: var(--bs-navbar-color);--bs-nav-link-hover-color: var(--bs-navbar-hover-color);--bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link.active,.navbar-nav .nav-link.show{color:var(--bs-navbar-active-color)}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-navbar-color)}.navbar-text a,.navbar-text a:hover,.navbar-text a:focus{color:var(--bs-navbar-active-color)}.navbar-collapse{flex-basis:100%;-webkit-flex-basis:100%;flex-grow:1;-webkit-flex-grow:1;align-items:center;-webkit-align-items:center}.navbar-toggler{padding:var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);font-size:var(--bs-navbar-toggler-font-size);line-height:1;color:var(--bs-navbar-color);background-color:rgba(0,0,0,0);border:var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);border-radius:var(--bs-navbar-toggler-border-radius);transition:var(--bs-navbar-toggler-transition)}@media(prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 var(--bs-navbar-toggler-focus-width)}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-image:var(--bs-navbar-toggler-icon-bg);background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media(min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-sm .offcanvas .offcanvas-header{display:none}.navbar-expand-sm .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-md .offcanvas .offcanvas-header{display:none}.navbar-expand-md .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-lg .offcanvas .offcanvas-header{display:none}.navbar-expand-lg .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xl .offcanvas .offcanvas-header{display:none}.navbar-expand-xl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}@media(min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand-xxl .offcanvas .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;-webkit-flex-wrap:nowrap;justify-content:flex-start;-webkit-justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row;-webkit-flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:var(--bs-navbar-nav-link-padding-x);padding-left:var(--bs-navbar-nav-link-padding-x)}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;display:-webkit-flex !important;flex-basis:auto;-webkit-flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas{position:static;z-index:auto;flex-grow:1;-webkit-flex-grow:1;width:auto !important;height:auto !important;visibility:visible !important;background-color:rgba(0,0,0,0) !important;border:0 !important;transform:none !important;transition:none}.navbar-expand .offcanvas .offcanvas-header{display:none}.navbar-expand .offcanvas .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible}.navbar-dark,.navbar[data-bs-theme=dark]{--bs-navbar-color: #fdfefe;--bs-navbar-hover-color: rgba(252, 252, 255, 0.8);--bs-navbar-disabled-color: rgba(253, 254, 254, 0.75);--bs-navbar-active-color: #fcfcff;--bs-navbar-brand-color: #fdfefe;--bs-navbar-brand-hover-color: #fcfcff;--bs-navbar-toggler-border-color: rgba(253, 254, 254, 0);--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfefe' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}[data-bs-theme=dark] .navbar-toggler-icon{--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='%23fdfefe' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.card{--bs-card-spacer-y: 1rem;--bs-card-spacer-x: 1rem;--bs-card-title-spacer-y: 0.5rem;--bs-card-title-color: ;--bs-card-subtitle-color: ;--bs-card-border-width: 1px;--bs-card-border-color: rgba(0, 0, 0, 0.175);--bs-card-border-radius: 0.25rem;--bs-card-box-shadow: ;--bs-card-inner-border-radius: calc(0.25rem - 1px);--bs-card-cap-padding-y: 0.5rem;--bs-card-cap-padding-x: 1rem;--bs-card-cap-bg: rgba(52, 58, 64, 0.25);--bs-card-cap-color: ;--bs-card-height: ;--bs-card-color: ;--bs-card-bg: #ffffff;--bs-card-img-overlay-padding: 1rem;--bs-card-group-margin: 0.75rem;position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;min-width:0;height:var(--bs-card-height);color:var(--bs-body-color);word-wrap:break-word;background-color:var(--bs-card-bg);background-clip:border-box;border:var(--bs-card-border-width) solid var(--bs-card-border-color);border-radius:var(--bs-card-border-radius)}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x);color:var(--bs-card-color)}.card-title{margin-bottom:var(--bs-card-title-spacer-y);color:var(--bs-card-title-color)}.card-subtitle{margin-top:calc(-0.5*var(--bs-card-title-spacer-y));margin-bottom:0;color:var(--bs-card-subtitle-color)}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:var(--bs-card-spacer-x)}.card-header{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);margin-bottom:0;color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-bottom:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-header:first-child{border-radius:var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0}.card-footer{padding:var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);color:var(--bs-card-cap-color);background-color:var(--bs-card-cap-bg);border-top:var(--bs-card-border-width) solid var(--bs-card-border-color)}.card-footer:last-child{border-radius:0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius)}.card-header-tabs{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-bottom:calc(-1*var(--bs-card-cap-padding-y));margin-left:calc(-0.5*var(--bs-card-cap-padding-x));border-bottom:0}.card-header-tabs .nav-link.active{background-color:var(--bs-card-bg);border-bottom-color:var(--bs-card-bg)}.card-header-pills{margin-right:calc(-0.5*var(--bs-card-cap-padding-x));margin-left:calc(-0.5*var(--bs-card-cap-padding-x))}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:var(--bs-card-img-overlay-padding);border-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:var(--bs-card-inner-border-radius);border-top-right-radius:var(--bs-card-inner-border-radius)}.card-img,.card-img-bottom{border-bottom-right-radius:var(--bs-card-inner-border-radius);border-bottom-left-radius:var(--bs-card-inner-border-radius)}.card-group>.card{margin-bottom:var(--bs-card-group-margin)}@media(min-width: 576px){.card-group{display:flex;display:-webkit-flex;flex-flow:row wrap;-webkit-flex-flow:row wrap}.card-group>.card{flex:1 0 0%;-webkit-flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion{--bs-accordion-color: #212529;--bs-accordion-bg: #ffffff;--bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;--bs-accordion-border-color: #dee2e6;--bs-accordion-border-width: 1px;--bs-accordion-border-radius: 0.25rem;--bs-accordion-inner-border-radius: calc(0.25rem - 1px);--bs-accordion-btn-padding-x: 1.25rem;--bs-accordion-btn-padding-y: 1rem;--bs-accordion-btn-color: #212529;--bs-accordion-btn-bg: #ffffff;--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-icon-width: 1.25rem;--bs-accordion-btn-icon-transform: rotate(-180deg);--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23052c65'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-focus-border-color: #86b7fe;--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-accordion-body-padding-x: 1.25rem;--bs-accordion-body-padding-y: 1rem;--bs-accordion-active-color: #052c65;--bs-accordion-active-bg: #cfe2ff}.accordion-button{position:relative;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;width:100%;padding:var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);font-size:1rem;color:var(--bs-accordion-btn-color);text-align:left;background-color:var(--bs-accordion-btn-bg);border:0;border-radius:0;overflow-anchor:none;transition:var(--bs-accordion-transition)}@media(prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:var(--bs-accordion-active-color);background-color:var(--bs-accordion-active-bg);box-shadow:inset 0 calc(-1*var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color)}.accordion-button:not(.collapsed)::after{background-image:var(--bs-accordion-btn-active-icon);transform:var(--bs-accordion-btn-icon-transform)}.accordion-button::after{flex-shrink:0;-webkit-flex-shrink:0;width:var(--bs-accordion-btn-icon-width);height:var(--bs-accordion-btn-icon-width);margin-left:auto;content:"";background-image:var(--bs-accordion-btn-icon);background-repeat:no-repeat;background-size:var(--bs-accordion-btn-icon-width);transition:var(--bs-accordion-btn-icon-transition)}@media(prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:var(--bs-accordion-btn-focus-border-color);outline:0;box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.accordion-header{margin-bottom:0}.accordion-item{color:var(--bs-accordion-color);background-color:var(--bs-accordion-bg);border:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.accordion-item:first-of-type{border-top-left-radius:var(--bs-accordion-border-radius);border-top-right-radius:var(--bs-accordion-border-radius)}.accordion-item:first-of-type .accordion-button{border-top-left-radius:var(--bs-accordion-inner-border-radius);border-top-right-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:var(--bs-accordion-inner-border-radius);border-bottom-left-radius:var(--bs-accordion-inner-border-radius)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:var(--bs-accordion-border-radius);border-bottom-left-radius:var(--bs-accordion-border-radius)}.accordion-body{padding:var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x)}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button,.accordion-flush .accordion-item .accordion-button.collapsed{border-radius:0}[data-bs-theme=dark] .accordion-button::after{--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.breadcrumb{--bs-breadcrumb-padding-x: 0;--bs-breadcrumb-padding-y: 0;--bs-breadcrumb-margin-bottom: 1rem;--bs-breadcrumb-bg: ;--bs-breadcrumb-border-radius: ;--bs-breadcrumb-divider-color: rgba(33, 37, 41, 0.75);--bs-breadcrumb-item-padding-x: 0.5rem;--bs-breadcrumb-item-active-color: rgba(33, 37, 41, 0.75);display:flex;display:-webkit-flex;flex-wrap:wrap;-webkit-flex-wrap:wrap;padding:var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);margin-bottom:var(--bs-breadcrumb-margin-bottom);font-size:var(--bs-breadcrumb-font-size);list-style:none;background-color:var(--bs-breadcrumb-bg);border-radius:var(--bs-breadcrumb-border-radius)}.breadcrumb-item+.breadcrumb-item{padding-left:var(--bs-breadcrumb-item-padding-x)}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:var(--bs-breadcrumb-item-padding-x);color:var(--bs-breadcrumb-divider-color);content:var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */}.breadcrumb-item.active{color:var(--bs-breadcrumb-item-active-color)}.pagination{--bs-pagination-padding-x: 0.75rem;--bs-pagination-padding-y: 0.375rem;--bs-pagination-font-size:1rem;--bs-pagination-color: blue;--bs-pagination-bg: #ffffff;--bs-pagination-border-width: 1px;--bs-pagination-border-color: #dee2e6;--bs-pagination-border-radius: 0.25rem;--bs-pagination-hover-color: #0000cc;--bs-pagination-hover-bg: #f8f9fa;--bs-pagination-hover-border-color: #dee2e6;--bs-pagination-focus-color: #0000cc;--bs-pagination-focus-bg: #e9ecef;--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-pagination-active-color: #ffffff;--bs-pagination-active-bg: #0d6efd;--bs-pagination-active-border-color: #0d6efd;--bs-pagination-disabled-color: rgba(33, 37, 41, 0.75);--bs-pagination-disabled-bg: #e9ecef;--bs-pagination-disabled-border-color: #dee2e6;display:flex;display:-webkit-flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;padding:var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);font-size:var(--bs-pagination-font-size);color:var(--bs-pagination-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-pagination-bg);border:var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media(prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:var(--bs-pagination-hover-color);background-color:var(--bs-pagination-hover-bg);border-color:var(--bs-pagination-hover-border-color)}.page-link:focus{z-index:3;color:var(--bs-pagination-focus-color);background-color:var(--bs-pagination-focus-bg);outline:0;box-shadow:var(--bs-pagination-focus-box-shadow)}.page-link.active,.active>.page-link{z-index:3;color:var(--bs-pagination-active-color);background-color:var(--bs-pagination-active-bg);border-color:var(--bs-pagination-active-border-color)}.page-link.disabled,.disabled>.page-link{color:var(--bs-pagination-disabled-color);pointer-events:none;background-color:var(--bs-pagination-disabled-bg);border-color:var(--bs-pagination-disabled-border-color)}.page-item:not(:first-child) .page-link{margin-left:calc(1px*-1)}.page-item:first-child .page-link{border-top-left-radius:var(--bs-pagination-border-radius);border-bottom-left-radius:var(--bs-pagination-border-radius)}.page-item:last-child .page-link{border-top-right-radius:var(--bs-pagination-border-radius);border-bottom-right-radius:var(--bs-pagination-border-radius)}.pagination-lg{--bs-pagination-padding-x: 1.5rem;--bs-pagination-padding-y: 0.75rem;--bs-pagination-font-size:1.25rem;--bs-pagination-border-radius: 0.5rem}.pagination-sm{--bs-pagination-padding-x: 0.5rem;--bs-pagination-padding-y: 0.25rem;--bs-pagination-font-size:0.875rem;--bs-pagination-border-radius: 0.2em}.badge{--bs-badge-padding-x: 0.65em;--bs-badge-padding-y: 0.35em;--bs-badge-font-size:0.75em;--bs-badge-font-weight: 700;--bs-badge-color: #ffffff;--bs-badge-border-radius: 0.25rem;display:inline-block;padding:var(--bs-badge-padding-y) var(--bs-badge-padding-x);font-size:var(--bs-badge-font-size);font-weight:var(--bs-badge-font-weight);line-height:1;color:var(--bs-badge-color);text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:var(--bs-badge-border-radius)}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{--bs-alert-bg: transparent;--bs-alert-padding-x: 1rem;--bs-alert-padding-y: 1rem;--bs-alert-margin-bottom: 1rem;--bs-alert-color: inherit;--bs-alert-border-color: transparent;--bs-alert-border: 1px solid var(--bs-alert-border-color);--bs-alert-border-radius: 0.25rem;--bs-alert-link-color: inherit;position:relative;padding:var(--bs-alert-padding-y) var(--bs-alert-padding-x);margin-bottom:var(--bs-alert-margin-bottom);color:var(--bs-alert-color);background-color:var(--bs-alert-bg);border:var(--bs-alert-border);border-radius:var(--bs-alert-border-radius)}.alert-heading{color:inherit}.alert-link{font-weight:700;color:var(--bs-alert-link-color)}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-default{--bs-alert-color: var(--bs-default-text-emphasis);--bs-alert-bg: var(--bs-default-bg-subtle);--bs-alert-border-color: var(--bs-default-border-subtle);--bs-alert-link-color: var(--bs-default-text-emphasis)}.alert-primary{--bs-alert-color: var(--bs-primary-text-emphasis);--bs-alert-bg: var(--bs-primary-bg-subtle);--bs-alert-border-color: var(--bs-primary-border-subtle);--bs-alert-link-color: var(--bs-primary-text-emphasis)}.alert-secondary{--bs-alert-color: var(--bs-secondary-text-emphasis);--bs-alert-bg: var(--bs-secondary-bg-subtle);--bs-alert-border-color: var(--bs-secondary-border-subtle);--bs-alert-link-color: var(--bs-secondary-text-emphasis)}.alert-success{--bs-alert-color: var(--bs-success-text-emphasis);--bs-alert-bg: var(--bs-success-bg-subtle);--bs-alert-border-color: var(--bs-success-border-subtle);--bs-alert-link-color: var(--bs-success-text-emphasis)}.alert-info{--bs-alert-color: var(--bs-info-text-emphasis);--bs-alert-bg: var(--bs-info-bg-subtle);--bs-alert-border-color: var(--bs-info-border-subtle);--bs-alert-link-color: var(--bs-info-text-emphasis)}.alert-warning{--bs-alert-color: var(--bs-warning-text-emphasis);--bs-alert-bg: var(--bs-warning-bg-subtle);--bs-alert-border-color: var(--bs-warning-border-subtle);--bs-alert-link-color: var(--bs-warning-text-emphasis)}.alert-danger{--bs-alert-color: var(--bs-danger-text-emphasis);--bs-alert-bg: var(--bs-danger-bg-subtle);--bs-alert-border-color: var(--bs-danger-border-subtle);--bs-alert-link-color: var(--bs-danger-text-emphasis)}.alert-light{--bs-alert-color: var(--bs-light-text-emphasis);--bs-alert-bg: var(--bs-light-bg-subtle);--bs-alert-border-color: var(--bs-light-border-subtle);--bs-alert-link-color: var(--bs-light-text-emphasis)}.alert-dark{--bs-alert-color: var(--bs-dark-text-emphasis);--bs-alert-bg: var(--bs-dark-bg-subtle);--bs-alert-border-color: var(--bs-dark-border-subtle);--bs-alert-link-color: var(--bs-dark-text-emphasis)}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress,.progress-stacked{--bs-progress-height: 1rem;--bs-progress-font-size:0.75rem;--bs-progress-bg: #e9ecef;--bs-progress-border-radius: 0.25rem;--bs-progress-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-progress-bar-color: #ffffff;--bs-progress-bar-bg: #0d6efd;--bs-progress-bar-transition: width 0.6s ease;display:flex;display:-webkit-flex;height:var(--bs-progress-height);overflow:hidden;font-size:var(--bs-progress-font-size);background-color:var(--bs-progress-bg);border-radius:var(--bs-progress-border-radius)}.progress-bar{display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;justify-content:center;-webkit-justify-content:center;overflow:hidden;color:var(--bs-progress-bar-color);text-align:center;white-space:nowrap;background-color:var(--bs-progress-bar-bg);transition:var(--bs-progress-bar-transition)}@media(prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-size:var(--bs-progress-height) var(--bs-progress-height)}.progress-stacked>.progress{overflow:visible}.progress-stacked>.progress>.progress-bar{width:100%}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media(prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{--bs-list-group-color: #212529;--bs-list-group-bg: #ffffff;--bs-list-group-border-color: #dee2e6;--bs-list-group-border-width: 1px;--bs-list-group-border-radius: 0.25rem;--bs-list-group-item-padding-x: 1rem;--bs-list-group-item-padding-y: 0.5rem;--bs-list-group-action-color: rgba(33, 37, 41, 0.75);--bs-list-group-action-hover-color: #000;--bs-list-group-action-hover-bg: #f8f9fa;--bs-list-group-action-active-color: #212529;--bs-list-group-action-active-bg: #e9ecef;--bs-list-group-disabled-color: rgba(33, 37, 41, 0.75);--bs-list-group-disabled-bg: #ffffff;--bs-list-group-active-color: #ffffff;--bs-list-group-active-bg: #0d6efd;--bs-list-group-active-border-color: #0d6efd;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;padding-left:0;margin-bottom:0;border-radius:var(--bs-list-group-border-radius)}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>.list-group-item::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:var(--bs-list-group-action-color);text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:var(--bs-list-group-action-hover-color);text-decoration:none;background-color:var(--bs-list-group-action-hover-bg)}.list-group-item-action:active{color:var(--bs-list-group-action-active-color);background-color:var(--bs-list-group-action-active-bg)}.list-group-item{position:relative;display:block;padding:var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);color:var(--bs-list-group-color);text-decoration:none;-webkit-text-decoration:none;-moz-text-decoration:none;-ms-text-decoration:none;-o-text-decoration:none;background-color:var(--bs-list-group-bg);border:var(--bs-list-group-border-width) solid var(--bs-list-group-border-color)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:var(--bs-list-group-disabled-color);pointer-events:none;background-color:var(--bs-list-group-disabled-bg)}.list-group-item.active{z-index:2;color:var(--bs-list-group-active-color);background-color:var(--bs-list-group-active-bg);border-color:var(--bs-list-group-active-border-color)}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:calc(-1*var(--bs-list-group-border-width));border-top-width:var(--bs-list-group-border-width)}.list-group-horizontal{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}@media(min-width: 576px){.list-group-horizontal-sm{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 768px){.list-group-horizontal-md{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 992px){.list-group-horizontal-lg{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1200px){.list-group-horizontal-xl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}@media(min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row;-webkit-flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child:not(:last-child){border-bottom-left-radius:var(--bs-list-group-border-radius);border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child:not(:first-child){border-top-right-radius:var(--bs-list-group-border-radius);border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:var(--bs-list-group-border-width);border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:calc(-1*var(--bs-list-group-border-width));border-left-width:var(--bs-list-group-border-width)}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 var(--bs-list-group-border-width)}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-default{--bs-list-group-color: var(--bs-default-text-emphasis);--bs-list-group-bg: var(--bs-default-bg-subtle);--bs-list-group-border-color: var(--bs-default-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-default-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-default-border-subtle);--bs-list-group-active-color: var(--bs-default-bg-subtle);--bs-list-group-active-bg: var(--bs-default-text-emphasis);--bs-list-group-active-border-color: var(--bs-default-text-emphasis)}.list-group-item-primary{--bs-list-group-color: var(--bs-primary-text-emphasis);--bs-list-group-bg: var(--bs-primary-bg-subtle);--bs-list-group-border-color: var(--bs-primary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-primary-border-subtle);--bs-list-group-active-color: var(--bs-primary-bg-subtle);--bs-list-group-active-bg: var(--bs-primary-text-emphasis);--bs-list-group-active-border-color: var(--bs-primary-text-emphasis)}.list-group-item-secondary{--bs-list-group-color: var(--bs-secondary-text-emphasis);--bs-list-group-bg: var(--bs-secondary-bg-subtle);--bs-list-group-border-color: var(--bs-secondary-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-secondary-border-subtle);--bs-list-group-active-color: var(--bs-secondary-bg-subtle);--bs-list-group-active-bg: var(--bs-secondary-text-emphasis);--bs-list-group-active-border-color: var(--bs-secondary-text-emphasis)}.list-group-item-success{--bs-list-group-color: var(--bs-success-text-emphasis);--bs-list-group-bg: var(--bs-success-bg-subtle);--bs-list-group-border-color: var(--bs-success-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-success-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-success-border-subtle);--bs-list-group-active-color: var(--bs-success-bg-subtle);--bs-list-group-active-bg: var(--bs-success-text-emphasis);--bs-list-group-active-border-color: var(--bs-success-text-emphasis)}.list-group-item-info{--bs-list-group-color: var(--bs-info-text-emphasis);--bs-list-group-bg: var(--bs-info-bg-subtle);--bs-list-group-border-color: var(--bs-info-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-info-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-info-border-subtle);--bs-list-group-active-color: var(--bs-info-bg-subtle);--bs-list-group-active-bg: var(--bs-info-text-emphasis);--bs-list-group-active-border-color: var(--bs-info-text-emphasis)}.list-group-item-warning{--bs-list-group-color: var(--bs-warning-text-emphasis);--bs-list-group-bg: var(--bs-warning-bg-subtle);--bs-list-group-border-color: var(--bs-warning-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-warning-border-subtle);--bs-list-group-active-color: var(--bs-warning-bg-subtle);--bs-list-group-active-bg: var(--bs-warning-text-emphasis);--bs-list-group-active-border-color: var(--bs-warning-text-emphasis)}.list-group-item-danger{--bs-list-group-color: var(--bs-danger-text-emphasis);--bs-list-group-bg: var(--bs-danger-bg-subtle);--bs-list-group-border-color: var(--bs-danger-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-danger-border-subtle);--bs-list-group-active-color: var(--bs-danger-bg-subtle);--bs-list-group-active-bg: var(--bs-danger-text-emphasis);--bs-list-group-active-border-color: var(--bs-danger-text-emphasis)}.list-group-item-light{--bs-list-group-color: var(--bs-light-text-emphasis);--bs-list-group-bg: var(--bs-light-bg-subtle);--bs-list-group-border-color: var(--bs-light-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-light-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-light-border-subtle);--bs-list-group-active-color: var(--bs-light-bg-subtle);--bs-list-group-active-bg: var(--bs-light-text-emphasis);--bs-list-group-active-border-color: var(--bs-light-text-emphasis)}.list-group-item-dark{--bs-list-group-color: var(--bs-dark-text-emphasis);--bs-list-group-bg: var(--bs-dark-bg-subtle);--bs-list-group-border-color: var(--bs-dark-border-subtle);--bs-list-group-action-hover-color: var(--bs-emphasis-color);--bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);--bs-list-group-action-active-color: var(--bs-emphasis-color);--bs-list-group-action-active-bg: var(--bs-dark-border-subtle);--bs-list-group-active-color: var(--bs-dark-bg-subtle);--bs-list-group-active-bg: var(--bs-dark-text-emphasis);--bs-list-group-active-border-color: var(--bs-dark-text-emphasis)}.btn-close{--bs-btn-close-color: #000;--bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");--bs-btn-close-opacity: 0.5;--bs-btn-close-hover-opacity: 0.75;--bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);--bs-btn-close-focus-opacity: 1;--bs-btn-close-disabled-opacity: 0.25;--bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:var(--bs-btn-close-color);background:rgba(0,0,0,0) var(--bs-btn-close-bg) center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:var(--bs-btn-close-opacity)}.btn-close:hover{color:var(--bs-btn-close-color);text-decoration:none;opacity:var(--bs-btn-close-hover-opacity)}.btn-close:focus{outline:0;box-shadow:var(--bs-btn-close-focus-shadow);opacity:var(--bs-btn-close-focus-opacity)}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;opacity:var(--bs-btn-close-disabled-opacity)}.btn-close-white{filter:var(--bs-btn-close-white-filter)}[data-bs-theme=dark] .btn-close{filter:var(--bs-btn-close-white-filter)}.toast{--bs-toast-zindex: 1090;--bs-toast-padding-x: 0.75rem;--bs-toast-padding-y: 0.5rem;--bs-toast-spacing: 1.5rem;--bs-toast-max-width: 350px;--bs-toast-font-size:0.875rem;--bs-toast-color: ;--bs-toast-bg: rgba(255, 255, 255, 0.85);--bs-toast-border-width: 1px;--bs-toast-border-color: rgba(0, 0, 0, 0.175);--bs-toast-border-radius: 0.25rem;--bs-toast-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-toast-header-color: rgba(33, 37, 41, 0.75);--bs-toast-header-bg: rgba(255, 255, 255, 0.85);--bs-toast-header-border-color: rgba(0, 0, 0, 0.175);width:var(--bs-toast-max-width);max-width:100%;font-size:var(--bs-toast-font-size);color:var(--bs-toast-color);pointer-events:auto;background-color:var(--bs-toast-bg);background-clip:padding-box;border:var(--bs-toast-border-width) solid var(--bs-toast-border-color);box-shadow:var(--bs-toast-box-shadow);border-radius:var(--bs-toast-border-radius)}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{--bs-toast-zindex: 1090;position:absolute;z-index:var(--bs-toast-zindex);width:max-content;width:-webkit-max-content;width:-moz-max-content;width:-ms-max-content;width:-o-max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:var(--bs-toast-spacing)}.toast-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;padding:var(--bs-toast-padding-y) var(--bs-toast-padding-x);color:var(--bs-toast-header-color);background-color:var(--bs-toast-header-bg);background-clip:padding-box;border-bottom:var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);border-top-left-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));border-top-right-radius:calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width))}.toast-header .btn-close{margin-right:calc(-0.5*var(--bs-toast-padding-x));margin-left:var(--bs-toast-padding-x)}.toast-body{padding:var(--bs-toast-padding-x);word-wrap:break-word}.modal{--bs-modal-zindex: 1055;--bs-modal-width: 500px;--bs-modal-padding: 1rem;--bs-modal-margin: 0.5rem;--bs-modal-color: ;--bs-modal-bg: #ffffff;--bs-modal-border-color: rgba(0, 0, 0, 0.175);--bs-modal-border-width: 1px;--bs-modal-border-radius: 0.5rem;--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-modal-inner-border-radius: calc(0.5rem - 1px);--bs-modal-header-padding-x: 1rem;--bs-modal-header-padding-y: 1rem;--bs-modal-header-padding: 1rem 1rem;--bs-modal-header-border-color: #dee2e6;--bs-modal-header-border-width: 1px;--bs-modal-title-line-height: 1.5;--bs-modal-footer-gap: 0.5rem;--bs-modal-footer-bg: ;--bs-modal-footer-border-color: #dee2e6;--bs-modal-footer-border-width: 1px;position:fixed;top:0;left:0;z-index:var(--bs-modal-zindex);display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:var(--bs-modal-margin);pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media(prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - var(--bs-modal-margin)*2)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;min-height:calc(100% - var(--bs-modal-margin)*2)}.modal-content{position:relative;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;width:100%;color:var(--bs-modal-color);pointer-events:auto;background-color:var(--bs-modal-bg);background-clip:padding-box;border:var(--bs-modal-border-width) solid var(--bs-modal-border-color);border-radius:var(--bs-modal-border-radius);outline:0}.modal-backdrop{--bs-backdrop-zindex: 1050;--bs-backdrop-bg: #000;--bs-backdrop-opacity: 0.5;position:fixed;top:0;left:0;z-index:var(--bs-backdrop-zindex);width:100vw;height:100vh;background-color:var(--bs-backdrop-bg)}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:var(--bs-backdrop-opacity)}.modal-header{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-modal-header-padding);border-bottom:var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);border-top-left-radius:var(--bs-modal-inner-border-radius);border-top-right-radius:var(--bs-modal-inner-border-radius)}.modal-header .btn-close{padding:calc(var(--bs-modal-header-padding-y)*.5) calc(var(--bs-modal-header-padding-x)*.5);margin:calc(-0.5*var(--bs-modal-header-padding-y)) calc(-0.5*var(--bs-modal-header-padding-x)) calc(-0.5*var(--bs-modal-header-padding-y)) auto}.modal-title{margin-bottom:0;line-height:var(--bs-modal-title-line-height)}.modal-body{position:relative;flex:1 1 auto;-webkit-flex:1 1 auto;padding:var(--bs-modal-padding)}.modal-footer{display:flex;display:-webkit-flex;flex-shrink:0;-webkit-flex-shrink:0;flex-wrap:wrap;-webkit-flex-wrap:wrap;align-items:center;-webkit-align-items:center;justify-content:flex-end;-webkit-justify-content:flex-end;padding:calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap)*.5);background-color:var(--bs-modal-footer-bg);border-top:var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);border-bottom-right-radius:var(--bs-modal-inner-border-radius);border-bottom-left-radius:var(--bs-modal-inner-border-radius)}.modal-footer>*{margin:calc(var(--bs-modal-footer-gap)*.5)}@media(min-width: 576px){.modal{--bs-modal-margin: 1.75rem;--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15)}.modal-dialog{max-width:var(--bs-modal-width);margin-right:auto;margin-left:auto}.modal-sm{--bs-modal-width: 300px}}@media(min-width: 992px){.modal-lg,.modal-xl{--bs-modal-width: 800px}}@media(min-width: 1200px){.modal-xl{--bs-modal-width: 1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header,.modal-fullscreen .modal-footer{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}@media(max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header,.modal-fullscreen-sm-down .modal-footer{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}}@media(max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header,.modal-fullscreen-md-down .modal-footer{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}}@media(max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header,.modal-fullscreen-lg-down .modal-footer{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}}@media(max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header,.modal-fullscreen-xl-down .modal-footer{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}}@media(max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header,.modal-fullscreen-xxl-down .modal-footer{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}}.tooltip{--bs-tooltip-zindex: 1080;--bs-tooltip-max-width: 200px;--bs-tooltip-padding-x: 0.5rem;--bs-tooltip-padding-y: 0.25rem;--bs-tooltip-margin: ;--bs-tooltip-font-size:0.875rem;--bs-tooltip-color: #ffffff;--bs-tooltip-bg: #000;--bs-tooltip-border-radius: 0.25rem;--bs-tooltip-opacity: 0.9;--bs-tooltip-arrow-width: 0.8rem;--bs-tooltip-arrow-height: 0.4rem;z-index:var(--bs-tooltip-zindex);display:block;margin:var(--bs-tooltip-margin);font-family:Roboto;font-style:normal;font-weight:400;line-height:1.6;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-tooltip-font-size);word-wrap:break-word;opacity:0}.tooltip.show{opacity:var(--bs-tooltip-opacity)}.tooltip .tooltip-arrow{display:block;width:var(--bs-tooltip-arrow-width);height:var(--bs-tooltip-arrow-height)}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-top-color:var(--bs-tooltip-bg)}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width)*.5) 0;border-right-color:var(--bs-tooltip-bg)}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:calc(-1*var(--bs-tooltip-arrow-height))}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-bottom-color:var(--bs-tooltip-bg)}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:calc(-1*var(--bs-tooltip-arrow-height));width:var(--bs-tooltip-arrow-height);height:var(--bs-tooltip-arrow-width)}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:calc(var(--bs-tooltip-arrow-width)*.5) 0 calc(var(--bs-tooltip-arrow-width)*.5) var(--bs-tooltip-arrow-height);border-left-color:var(--bs-tooltip-bg)}.tooltip-inner{max-width:var(--bs-tooltip-max-width);padding:var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);color:var(--bs-tooltip-color);text-align:center;background-color:var(--bs-tooltip-bg);border-radius:var(--bs-tooltip-border-radius)}.popover{--bs-popover-zindex: 1070;--bs-popover-max-width: 276px;--bs-popover-font-size:0.875rem;--bs-popover-bg: #ffffff;--bs-popover-border-width: 1px;--bs-popover-border-color: rgba(0, 0, 0, 0.175);--bs-popover-border-radius: 0.5rem;--bs-popover-inner-border-radius: calc(0.5rem - 1px);--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-popover-header-padding-x: 1rem;--bs-popover-header-padding-y: 0.5rem;--bs-popover-header-font-size:1rem;--bs-popover-header-color: inherit;--bs-popover-header-bg: #e9ecef;--bs-popover-body-padding-x: 1rem;--bs-popover-body-padding-y: 1rem;--bs-popover-body-color: #212529;--bs-popover-arrow-width: 1rem;--bs-popover-arrow-height: 0.5rem;--bs-popover-arrow-border: var(--bs-popover-border-color);z-index:var(--bs-popover-zindex);display:block;max-width:var(--bs-popover-max-width);font-family:Roboto;font-style:normal;font-weight:400;line-height:1.6;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;white-space:normal;word-spacing:normal;line-break:auto;font-size:var(--bs-popover-font-size);word-wrap:break-word;background-color:var(--bs-popover-bg);background-clip:padding-box;border:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-radius:var(--bs-popover-border-radius)}.popover .popover-arrow{display:block;width:var(--bs-popover-arrow-width);height:var(--bs-popover-arrow-height)}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid;border-width:0}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{border-width:var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-top-color:var(--bs-popover-arrow-border)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:var(--bs-popover-border-width);border-top-color:var(--bs-popover-bg)}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width)*.5) 0}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-right-color:var(--bs-popover-arrow-border)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:var(--bs-popover-border-width);border-right-color:var(--bs-popover-bg)}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width))}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{border-width:0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-bottom-color:var(--bs-popover-arrow-border)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:var(--bs-popover-border-width);border-bottom-color:var(--bs-popover-bg)}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:var(--bs-popover-arrow-width);margin-left:calc(-0.5*var(--bs-popover-arrow-width));content:"";border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-header-bg)}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-1*(var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));width:var(--bs-popover-arrow-height);height:var(--bs-popover-arrow-width)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{border-width:calc(var(--bs-popover-arrow-width)*.5) 0 calc(var(--bs-popover-arrow-width)*.5) var(--bs-popover-arrow-height)}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-left-color:var(--bs-popover-arrow-border)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:var(--bs-popover-border-width);border-left-color:var(--bs-popover-bg)}.popover-header{padding:var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);margin-bottom:0;font-size:var(--bs-popover-header-font-size);color:var(--bs-popover-header-color);background-color:var(--bs-popover-header-bg);border-bottom:var(--bs-popover-border-width) solid var(--bs-popover-border-color);border-top-left-radius:var(--bs-popover-inner-border-radius);border-top-right-radius:var(--bs-popover-inner-border-radius)}.popover-header:empty{display:none}.popover-body{padding:var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);color:var(--bs-popover-body-color)}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y;-webkit-touch-action:pan-y;-moz-touch-action:pan-y;-ms-touch-action:pan-y;-o-touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden;transition:transform .6s ease-in-out}@media(prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media(prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:center;-webkit-justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media(prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ffffff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;-webkit-flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media(prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}[data-bs-theme=dark] .carousel .carousel-control-prev-icon,[data-bs-theme=dark] .carousel .carousel-control-next-icon,[data-bs-theme=dark].carousel .carousel-control-prev-icon,[data-bs-theme=dark].carousel .carousel-control-next-icon{filter:invert(1) grayscale(100)}[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target],[data-bs-theme=dark].carousel .carousel-indicators [data-bs-target]{background-color:#000}[data-bs-theme=dark] .carousel .carousel-caption,[data-bs-theme=dark].carousel .carousel-caption{color:#000}.spinner-grow,.spinner-border{display:inline-block;width:var(--bs-spinner-width);height:var(--bs-spinner-height);vertical-align:var(--bs-spinner-vertical-align);border-radius:50%;animation:var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name)}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-border-width: 0.25em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-border;border:var(--bs-spinner-border-width) solid currentcolor;border-right-color:rgba(0,0,0,0)}.spinner-border-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem;--bs-spinner-border-width: 0.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{--bs-spinner-width: 2rem;--bs-spinner-height: 2rem;--bs-spinner-vertical-align: -0.125em;--bs-spinner-animation-speed: 0.75s;--bs-spinner-animation-name: spinner-grow;background-color:currentcolor;opacity:0}.spinner-grow-sm{--bs-spinner-width: 1rem;--bs-spinner-height: 1rem}@media(prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{--bs-spinner-animation-speed: 1.5s}}.offcanvas,.offcanvas-xxl,.offcanvas-xl,.offcanvas-lg,.offcanvas-md,.offcanvas-sm{--bs-offcanvas-zindex: 1045;--bs-offcanvas-width: 400px;--bs-offcanvas-height: 30vh;--bs-offcanvas-padding-x: 1rem;--bs-offcanvas-padding-y: 1rem;--bs-offcanvas-color: #212529;--bs-offcanvas-bg: #ffffff;--bs-offcanvas-border-width: 1px;--bs-offcanvas-border-color: rgba(0, 0, 0, 0.175);--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-offcanvas-transition: transform 0.3s ease-in-out;--bs-offcanvas-title-line-height: 1.5}@media(max-width: 575.98px){.offcanvas-sm{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 575.98px)and (prefers-reduced-motion: reduce){.offcanvas-sm{transition:none}}@media(max-width: 575.98px){.offcanvas-sm.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-sm.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-sm.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-sm.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-sm.showing,.offcanvas-sm.show:not(.hiding){transform:none}.offcanvas-sm.showing,.offcanvas-sm.hiding,.offcanvas-sm.show{visibility:visible}}@media(min-width: 576px){.offcanvas-sm{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-sm .offcanvas-header{display:none}.offcanvas-sm .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 767.98px){.offcanvas-md{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 767.98px)and (prefers-reduced-motion: reduce){.offcanvas-md{transition:none}}@media(max-width: 767.98px){.offcanvas-md.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-md.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-md.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-md.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-md.showing,.offcanvas-md.show:not(.hiding){transform:none}.offcanvas-md.showing,.offcanvas-md.hiding,.offcanvas-md.show{visibility:visible}}@media(min-width: 768px){.offcanvas-md{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-md .offcanvas-header{display:none}.offcanvas-md .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 991.98px){.offcanvas-lg{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 991.98px)and (prefers-reduced-motion: reduce){.offcanvas-lg{transition:none}}@media(max-width: 991.98px){.offcanvas-lg.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-lg.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-lg.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-lg.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-lg.showing,.offcanvas-lg.show:not(.hiding){transform:none}.offcanvas-lg.showing,.offcanvas-lg.hiding,.offcanvas-lg.show{visibility:visible}}@media(min-width: 992px){.offcanvas-lg{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-lg .offcanvas-header{display:none}.offcanvas-lg .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1199.98px){.offcanvas-xl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1199.98px)and (prefers-reduced-motion: reduce){.offcanvas-xl{transition:none}}@media(max-width: 1199.98px){.offcanvas-xl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xl.showing,.offcanvas-xl.show:not(.hiding){transform:none}.offcanvas-xl.showing,.offcanvas-xl.hiding,.offcanvas-xl.show{visibility:visible}}@media(min-width: 1200px){.offcanvas-xl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xl .offcanvas-header{display:none}.offcanvas-xl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}@media(max-width: 1399.98px){.offcanvas-xxl{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}}@media(max-width: 1399.98px)and (prefers-reduced-motion: reduce){.offcanvas-xxl{transition:none}}@media(max-width: 1399.98px){.offcanvas-xxl.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas-xxl.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas-xxl.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas-xxl.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas-xxl.showing,.offcanvas-xxl.show:not(.hiding){transform:none}.offcanvas-xxl.showing,.offcanvas-xxl.hiding,.offcanvas-xxl.show{visibility:visible}}@media(min-width: 1400px){.offcanvas-xxl{--bs-offcanvas-height: auto;--bs-offcanvas-border-width: 0;background-color:rgba(0,0,0,0) !important}.offcanvas-xxl .offcanvas-header{display:none}.offcanvas-xxl .offcanvas-body{display:flex;display:-webkit-flex;flex-grow:0;-webkit-flex-grow:0;padding:0;overflow-y:visible;background-color:rgba(0,0,0,0) !important}}.offcanvas{position:fixed;bottom:0;z-index:var(--bs-offcanvas-zindex);display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;max-width:100%;color:var(--bs-offcanvas-color);visibility:hidden;background-color:var(--bs-offcanvas-bg);background-clip:padding-box;outline:0;transition:var(--bs-offcanvas-transition)}@media(prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas.offcanvas-start{top:0;left:0;width:var(--bs-offcanvas-width);border-right:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(-100%)}.offcanvas.offcanvas-end{top:0;right:0;width:var(--bs-offcanvas-width);border-left:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateX(100%)}.offcanvas.offcanvas-top{top:0;right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-bottom:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(-100%)}.offcanvas.offcanvas-bottom{right:0;left:0;height:var(--bs-offcanvas-height);max-height:100%;border-top:var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);transform:translateY(100%)}.offcanvas.showing,.offcanvas.show:not(.hiding){transform:none}.offcanvas.showing,.offcanvas.hiding,.offcanvas.show{visibility:visible}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;display:-webkit-flex;align-items:center;-webkit-align-items:center;justify-content:space-between;-webkit-justify-content:space-between;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x)}.offcanvas-header .btn-close{padding:calc(var(--bs-offcanvas-padding-y)*.5) calc(var(--bs-offcanvas-padding-x)*.5);margin-top:calc(-0.5*var(--bs-offcanvas-padding-y));margin-right:calc(-0.5*var(--bs-offcanvas-padding-x));margin-bottom:calc(-0.5*var(--bs-offcanvas-padding-y))}.offcanvas-title{margin-bottom:0;line-height:var(--bs-offcanvas-title-line-height)}.offcanvas-body{flex-grow:1;-webkit-flex-grow:1;padding:var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);overflow-y:auto}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentcolor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{animation:placeholder-glow 2s ease-in-out infinite}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);-webkit-mask-image:linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);mask-size:200% 100%;-webkit-mask-size:200% 100%;animation:placeholder-wave 2s linear infinite}@keyframes placeholder-wave{100%{mask-position:-200% 0%;-webkit-mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.text-bg-default{color:#000 !important;background-color:RGBA(var(--bs-default-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-primary{color:#fff !important;background-color:RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-secondary{color:#fff !important;background-color:RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-success{color:#fff !important;background-color:RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-info{color:#000 !important;background-color:RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-warning{color:#000 !important;background-color:RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-danger{color:#fff !important;background-color:RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-light{color:#000 !important;background-color:RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important}.text-bg-dark{color:#fff !important;background-color:RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important}.link-default{color:RGBA(var(--bs-default-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-default-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-default:hover,.link-default:focus{color:RGBA(229, 232, 235, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(229, 232, 235, var(--bs-link-underline-opacity, 1)) !important}.link-primary{color:RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-primary:hover,.link-primary:focus{color:RGBA(10, 88, 202, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(10, 88, 202, var(--bs-link-underline-opacity, 1)) !important}.link-secondary{color:RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-secondary:hover,.link-secondary:focus{color:RGBA(86, 94, 100, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1)) !important}.link-success{color:RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-success:hover,.link-success:focus{color:RGBA(20, 108, 67, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(20, 108, 67, var(--bs-link-underline-opacity, 1)) !important}.link-info{color:RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-info:hover,.link-info:focus{color:RGBA(61, 213, 243, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(61, 213, 243, var(--bs-link-underline-opacity, 1)) !important}.link-warning{color:RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-warning:hover,.link-warning:focus{color:RGBA(255, 205, 57, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(255, 205, 57, var(--bs-link-underline-opacity, 1)) !important}.link-danger{color:RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-danger:hover,.link-danger:focus{color:RGBA(176, 42, 55, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(176, 42, 55, var(--bs-link-underline-opacity, 1)) !important}.link-light{color:RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-light:hover,.link-light:focus{color:RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important}.link-dark{color:RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-dark:hover,.link-dark:focus{color:RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-body-emphasis:hover,.link-body-emphasis:focus{color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important;text-decoration-color:RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important}.focus-ring:focus{outline:0;box-shadow:var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color)}.icon-link{display:inline-flex;gap:.375rem;align-items:center;-webkit-align-items:center;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5));text-underline-offset:.25em;backface-visibility:hidden;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-o-backface-visibility:hidden}.icon-link>.bi{flex-shrink:0;-webkit-flex-shrink:0;width:1em;height:1em;fill:currentcolor;transition:.2s ease-in-out transform}@media(prefers-reduced-motion: reduce){.icon-link>.bi{transition:none}}.icon-link-hover:hover>.bi,.icon-link-hover:focus-visible>.bi{transform:var(--bs-icon-link-transform, translate3d(0.25em, 0, 0))}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}.sticky-bottom{position:sticky;bottom:0;z-index:1020}@media(min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}.sticky-sm-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}.sticky-md-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}.sticky-lg-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}.sticky-xl-bottom{position:sticky;bottom:0;z-index:1020}}@media(min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}.sticky-xxl-bottom{position:sticky;bottom:0;z-index:1020}}.hstack{display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;align-items:center;-webkit-align-items:center;align-self:stretch;-webkit-align-self:stretch}.vstack{display:flex;display:-webkit-flex;flex:1 1 auto;-webkit-flex:1 1 auto;flex-direction:column;-webkit-flex-direction:column;align-self:stretch;-webkit-align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.visually-hidden:not(caption),.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption){position:absolute !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;-webkit-align-self:stretch;width:1px;min-height:1em;background-color:currentcolor;opacity:.25}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.object-fit-contain{object-fit:contain !important}.object-fit-cover{object-fit:cover !important}.object-fit-fill{object-fit:fill !important}.object-fit-scale{object-fit:scale-down !important}.object-fit-none{object-fit:none !important}.opacity-0{opacity:0 !important}.opacity-25{opacity:.25 !important}.opacity-50{opacity:.5 !important}.opacity-75{opacity:.75 !important}.opacity-100{opacity:1 !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.overflow-x-auto{overflow-x:auto !important}.overflow-x-hidden{overflow-x:hidden !important}.overflow-x-visible{overflow-x:visible !important}.overflow-x-scroll{overflow-x:scroll !important}.overflow-y-auto{overflow-y:auto !important}.overflow-y-hidden{overflow-y:hidden !important}.overflow-y-visible{overflow-y:visible !important}.overflow-y-scroll{overflow-y:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-inline-grid{display:inline-grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.focus-ring-default{--bs-focus-ring-color: rgba(var(--bs-default-rgb), var(--bs-focus-ring-opacity))}.focus-ring-primary{--bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-secondary{--bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity))}.focus-ring-success{--bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity))}.focus-ring-info{--bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity))}.focus-ring-warning{--bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity))}.focus-ring-danger{--bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity))}.focus-ring-light{--bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity))}.focus-ring-dark{--bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity))}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-0{border:0 !important}.border-top{border-top:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-top-0{border-top:0 !important}.border-end{border-right:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important}.border-start-0{border-left:0 !important}.border-default{--bs-border-opacity: 1;border-color:rgba(var(--bs-default-rgb), var(--bs-border-opacity)) !important}.border-primary{--bs-border-opacity: 1;border-color:rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important}.border-secondary{--bs-border-opacity: 1;border-color:rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important}.border-success{--bs-border-opacity: 1;border-color:rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important}.border-info{--bs-border-opacity: 1;border-color:rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important}.border-warning{--bs-border-opacity: 1;border-color:rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important}.border-danger{--bs-border-opacity: 1;border-color:rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important}.border-light{--bs-border-opacity: 1;border-color:rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important}.border-dark{--bs-border-opacity: 1;border-color:rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important}.border-black{--bs-border-opacity: 1;border-color:rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important}.border-white{--bs-border-opacity: 1;border-color:rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important}.border-primary-subtle{border-color:var(--bs-primary-border-subtle) !important}.border-secondary-subtle{border-color:var(--bs-secondary-border-subtle) !important}.border-success-subtle{border-color:var(--bs-success-border-subtle) !important}.border-info-subtle{border-color:var(--bs-info-border-subtle) !important}.border-warning-subtle{border-color:var(--bs-warning-border-subtle) !important}.border-danger-subtle{border-color:var(--bs-danger-border-subtle) !important}.border-light-subtle{border-color:var(--bs-light-border-subtle) !important}.border-dark-subtle{border-color:var(--bs-dark-border-subtle) !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.border-opacity-10{--bs-border-opacity: 0.1}.border-opacity-25{--bs-border-opacity: 0.25}.border-opacity-50{--bs-border-opacity: 0.5}.border-opacity-75{--bs-border-opacity: 0.75}.border-opacity-100{--bs-border-opacity: 1}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.row-gap-0{row-gap:0 !important}.row-gap-1{row-gap:.25rem !important}.row-gap-2{row-gap:.5rem !important}.row-gap-3{row-gap:1rem !important}.row-gap-4{row-gap:1.5rem !important}.row-gap-5{row-gap:3rem !important}.column-gap-0{column-gap:0 !important}.column-gap-1{column-gap:.25rem !important}.column-gap-2{column-gap:.5rem !important}.column-gap-3{column-gap:1rem !important}.column-gap-4{column-gap:1.5rem !important}.column-gap-5{column-gap:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.325rem + 0.9vw) !important}.fs-2{font-size:calc(1.29rem + 0.48vw) !important}.fs-3{font-size:calc(1.27rem + 0.24vw) !important}.fs-4{font-size:1.25rem !important}.fs-5{font-size:1.1rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-lighter{font-weight:lighter !important}.fw-light{font-weight:300 !important}.fw-normal{font-weight:400 !important}.fw-medium{font-weight:500 !important}.fw-semibold{font-weight:600 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.6 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-default{--bs-text-opacity: 1;color:rgba(var(--bs-default-rgb), var(--bs-text-opacity)) !important}.text-primary{--bs-text-opacity: 1;color:rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important}.text-secondary{--bs-text-opacity: 1;color:rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important}.text-success{--bs-text-opacity: 1;color:rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important}.text-info{--bs-text-opacity: 1;color:rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important}.text-warning{--bs-text-opacity: 1;color:rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important}.text-danger{--bs-text-opacity: 1;color:rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important}.text-light{--bs-text-opacity: 1;color:rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important}.text-dark{--bs-text-opacity: 1;color:rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important}.text-black{--bs-text-opacity: 1;color:rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important}.text-white{--bs-text-opacity: 1;color:rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important}.text-body{--bs-text-opacity: 1;color:rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important}.text-muted{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-black-50{--bs-text-opacity: 1;color:rgba(0,0,0,.5) !important}.text-white-50{--bs-text-opacity: 1;color:rgba(255,255,255,.5) !important}.text-body-secondary{--bs-text-opacity: 1;color:var(--bs-secondary-color) !important}.text-body-tertiary{--bs-text-opacity: 1;color:var(--bs-tertiary-color) !important}.text-body-emphasis{--bs-text-opacity: 1;color:var(--bs-emphasis-color) !important}.text-reset{--bs-text-opacity: 1;color:inherit !important}.text-opacity-25{--bs-text-opacity: 0.25}.text-opacity-50{--bs-text-opacity: 0.5}.text-opacity-75{--bs-text-opacity: 0.75}.text-opacity-100{--bs-text-opacity: 1}.text-primary-emphasis{color:var(--bs-primary-text-emphasis) !important}.text-secondary-emphasis{color:var(--bs-secondary-text-emphasis) !important}.text-success-emphasis{color:var(--bs-success-text-emphasis) !important}.text-info-emphasis{color:var(--bs-info-text-emphasis) !important}.text-warning-emphasis{color:var(--bs-warning-text-emphasis) !important}.text-danger-emphasis{color:var(--bs-danger-text-emphasis) !important}.text-light-emphasis{color:var(--bs-light-text-emphasis) !important}.text-dark-emphasis{color:var(--bs-dark-text-emphasis) !important}.link-opacity-10{--bs-link-opacity: 0.1}.link-opacity-10-hover:hover{--bs-link-opacity: 0.1}.link-opacity-25{--bs-link-opacity: 0.25}.link-opacity-25-hover:hover{--bs-link-opacity: 0.25}.link-opacity-50{--bs-link-opacity: 0.5}.link-opacity-50-hover:hover{--bs-link-opacity: 0.5}.link-opacity-75{--bs-link-opacity: 0.75}.link-opacity-75-hover:hover{--bs-link-opacity: 0.75}.link-opacity-100{--bs-link-opacity: 1}.link-opacity-100-hover:hover{--bs-link-opacity: 1}.link-offset-1{text-underline-offset:.125em !important}.link-offset-1-hover:hover{text-underline-offset:.125em !important}.link-offset-2{text-underline-offset:.25em !important}.link-offset-2-hover:hover{text-underline-offset:.25em !important}.link-offset-3{text-underline-offset:.375em !important}.link-offset-3-hover:hover{text-underline-offset:.375em !important}.link-underline-default{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-default-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-primary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-secondary{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-success{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-info{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-warning{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-danger{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-light{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important}.link-underline-dark{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important}.link-underline{--bs-link-underline-opacity: 1;text-decoration-color:rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important}.link-underline-opacity-0{--bs-link-underline-opacity: 0}.link-underline-opacity-0-hover:hover{--bs-link-underline-opacity: 0}.link-underline-opacity-10{--bs-link-underline-opacity: 0.1}.link-underline-opacity-10-hover:hover{--bs-link-underline-opacity: 0.1}.link-underline-opacity-25{--bs-link-underline-opacity: 0.25}.link-underline-opacity-25-hover:hover{--bs-link-underline-opacity: 0.25}.link-underline-opacity-50{--bs-link-underline-opacity: 0.5}.link-underline-opacity-50-hover:hover{--bs-link-underline-opacity: 0.5}.link-underline-opacity-75{--bs-link-underline-opacity: 0.75}.link-underline-opacity-75-hover:hover{--bs-link-underline-opacity: 0.75}.link-underline-opacity-100{--bs-link-underline-opacity: 1}.link-underline-opacity-100-hover:hover{--bs-link-underline-opacity: 1}.bg-default{--bs-bg-opacity: 1;background-color:rgba(var(--bs-default-rgb), var(--bs-bg-opacity)) !important}.bg-primary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important}.bg-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important}.bg-success{--bs-bg-opacity: 1;background-color:rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important}.bg-info{--bs-bg-opacity: 1;background-color:rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important}.bg-warning{--bs-bg-opacity: 1;background-color:rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important}.bg-danger{--bs-bg-opacity: 1;background-color:rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important}.bg-light{--bs-bg-opacity: 1;background-color:rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important}.bg-dark{--bs-bg-opacity: 1;background-color:rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important}.bg-black{--bs-bg-opacity: 1;background-color:rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important}.bg-white{--bs-bg-opacity: 1;background-color:rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important}.bg-body{--bs-bg-opacity: 1;background-color:rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important}.bg-transparent{--bs-bg-opacity: 1;background-color:rgba(0,0,0,0) !important}.bg-body-secondary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-body-tertiary{--bs-bg-opacity: 1;background-color:rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important}.bg-opacity-10{--bs-bg-opacity: 0.1}.bg-opacity-25{--bs-bg-opacity: 0.25}.bg-opacity-50{--bs-bg-opacity: 0.5}.bg-opacity-75{--bs-bg-opacity: 0.75}.bg-opacity-100{--bs-bg-opacity: 1}.bg-primary-subtle{background-color:var(--bs-primary-bg-subtle) !important}.bg-secondary-subtle{background-color:var(--bs-secondary-bg-subtle) !important}.bg-success-subtle{background-color:var(--bs-success-bg-subtle) !important}.bg-info-subtle{background-color:var(--bs-info-bg-subtle) !important}.bg-warning-subtle{background-color:var(--bs-warning-bg-subtle) !important}.bg-danger-subtle{background-color:var(--bs-danger-bg-subtle) !important}.bg-light-subtle{background-color:var(--bs-light-bg-subtle) !important}.bg-dark-subtle{background-color:var(--bs-dark-bg-subtle) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:var(--bs-border-radius) !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:var(--bs-border-radius-sm) !important}.rounded-2{border-radius:var(--bs-border-radius) !important}.rounded-3{border-radius:var(--bs-border-radius-lg) !important}.rounded-4{border-radius:var(--bs-border-radius-xl) !important}.rounded-5{border-radius:var(--bs-border-radius-xxl) !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:var(--bs-border-radius-pill) !important}.rounded-top{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-0{border-top-left-radius:0 !important;border-top-right-radius:0 !important}.rounded-top-1{border-top-left-radius:var(--bs-border-radius-sm) !important;border-top-right-radius:var(--bs-border-radius-sm) !important}.rounded-top-2{border-top-left-radius:var(--bs-border-radius) !important;border-top-right-radius:var(--bs-border-radius) !important}.rounded-top-3{border-top-left-radius:var(--bs-border-radius-lg) !important;border-top-right-radius:var(--bs-border-radius-lg) !important}.rounded-top-4{border-top-left-radius:var(--bs-border-radius-xl) !important;border-top-right-radius:var(--bs-border-radius-xl) !important}.rounded-top-5{border-top-left-radius:var(--bs-border-radius-xxl) !important;border-top-right-radius:var(--bs-border-radius-xxl) !important}.rounded-top-circle{border-top-left-radius:50% !important;border-top-right-radius:50% !important}.rounded-top-pill{border-top-left-radius:var(--bs-border-radius-pill) !important;border-top-right-radius:var(--bs-border-radius-pill) !important}.rounded-end{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-0{border-top-right-radius:0 !important;border-bottom-right-radius:0 !important}.rounded-end-1{border-top-right-radius:var(--bs-border-radius-sm) !important;border-bottom-right-radius:var(--bs-border-radius-sm) !important}.rounded-end-2{border-top-right-radius:var(--bs-border-radius) !important;border-bottom-right-radius:var(--bs-border-radius) !important}.rounded-end-3{border-top-right-radius:var(--bs-border-radius-lg) !important;border-bottom-right-radius:var(--bs-border-radius-lg) !important}.rounded-end-4{border-top-right-radius:var(--bs-border-radius-xl) !important;border-bottom-right-radius:var(--bs-border-radius-xl) !important}.rounded-end-5{border-top-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-right-radius:var(--bs-border-radius-xxl) !important}.rounded-end-circle{border-top-right-radius:50% !important;border-bottom-right-radius:50% !important}.rounded-end-pill{border-top-right-radius:var(--bs-border-radius-pill) !important;border-bottom-right-radius:var(--bs-border-radius-pill) !important}.rounded-bottom{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-0{border-bottom-right-radius:0 !important;border-bottom-left-radius:0 !important}.rounded-bottom-1{border-bottom-right-radius:var(--bs-border-radius-sm) !important;border-bottom-left-radius:var(--bs-border-radius-sm) !important}.rounded-bottom-2{border-bottom-right-radius:var(--bs-border-radius) !important;border-bottom-left-radius:var(--bs-border-radius) !important}.rounded-bottom-3{border-bottom-right-radius:var(--bs-border-radius-lg) !important;border-bottom-left-radius:var(--bs-border-radius-lg) !important}.rounded-bottom-4{border-bottom-right-radius:var(--bs-border-radius-xl) !important;border-bottom-left-radius:var(--bs-border-radius-xl) !important}.rounded-bottom-5{border-bottom-right-radius:var(--bs-border-radius-xxl) !important;border-bottom-left-radius:var(--bs-border-radius-xxl) !important}.rounded-bottom-circle{border-bottom-right-radius:50% !important;border-bottom-left-radius:50% !important}.rounded-bottom-pill{border-bottom-right-radius:var(--bs-border-radius-pill) !important;border-bottom-left-radius:var(--bs-border-radius-pill) !important}.rounded-start{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-0{border-bottom-left-radius:0 !important;border-top-left-radius:0 !important}.rounded-start-1{border-bottom-left-radius:var(--bs-border-radius-sm) !important;border-top-left-radius:var(--bs-border-radius-sm) !important}.rounded-start-2{border-bottom-left-radius:var(--bs-border-radius) !important;border-top-left-radius:var(--bs-border-radius) !important}.rounded-start-3{border-bottom-left-radius:var(--bs-border-radius-lg) !important;border-top-left-radius:var(--bs-border-radius-lg) !important}.rounded-start-4{border-bottom-left-radius:var(--bs-border-radius-xl) !important;border-top-left-radius:var(--bs-border-radius-xl) !important}.rounded-start-5{border-bottom-left-radius:var(--bs-border-radius-xxl) !important;border-top-left-radius:var(--bs-border-radius-xxl) !important}.rounded-start-circle{border-bottom-left-radius:50% !important;border-top-left-radius:50% !important}.rounded-start-pill{border-bottom-left-radius:var(--bs-border-radius-pill) !important;border-top-left-radius:var(--bs-border-radius-pill) !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}.z-n1{z-index:-1 !important}.z-0{z-index:0 !important}.z-1{z-index:1 !important}.z-2{z-index:2 !important}.z-3{z-index:3 !important}@media(min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.object-fit-sm-contain{object-fit:contain !important}.object-fit-sm-cover{object-fit:cover !important}.object-fit-sm-fill{object-fit:fill !important}.object-fit-sm-scale{object-fit:scale-down !important}.object-fit-sm-none{object-fit:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-inline-grid{display:inline-grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.row-gap-sm-0{row-gap:0 !important}.row-gap-sm-1{row-gap:.25rem !important}.row-gap-sm-2{row-gap:.5rem !important}.row-gap-sm-3{row-gap:1rem !important}.row-gap-sm-4{row-gap:1.5rem !important}.row-gap-sm-5{row-gap:3rem !important}.column-gap-sm-0{column-gap:0 !important}.column-gap-sm-1{column-gap:.25rem !important}.column-gap-sm-2{column-gap:.5rem !important}.column-gap-sm-3{column-gap:1rem !important}.column-gap-sm-4{column-gap:1.5rem !important}.column-gap-sm-5{column-gap:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media(min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.object-fit-md-contain{object-fit:contain !important}.object-fit-md-cover{object-fit:cover !important}.object-fit-md-fill{object-fit:fill !important}.object-fit-md-scale{object-fit:scale-down !important}.object-fit-md-none{object-fit:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-inline-grid{display:inline-grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.row-gap-md-0{row-gap:0 !important}.row-gap-md-1{row-gap:.25rem !important}.row-gap-md-2{row-gap:.5rem !important}.row-gap-md-3{row-gap:1rem !important}.row-gap-md-4{row-gap:1.5rem !important}.row-gap-md-5{row-gap:3rem !important}.column-gap-md-0{column-gap:0 !important}.column-gap-md-1{column-gap:.25rem !important}.column-gap-md-2{column-gap:.5rem !important}.column-gap-md-3{column-gap:1rem !important}.column-gap-md-4{column-gap:1.5rem !important}.column-gap-md-5{column-gap:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media(min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.object-fit-lg-contain{object-fit:contain !important}.object-fit-lg-cover{object-fit:cover !important}.object-fit-lg-fill{object-fit:fill !important}.object-fit-lg-scale{object-fit:scale-down !important}.object-fit-lg-none{object-fit:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-inline-grid{display:inline-grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.row-gap-lg-0{row-gap:0 !important}.row-gap-lg-1{row-gap:.25rem !important}.row-gap-lg-2{row-gap:.5rem !important}.row-gap-lg-3{row-gap:1rem !important}.row-gap-lg-4{row-gap:1.5rem !important}.row-gap-lg-5{row-gap:3rem !important}.column-gap-lg-0{column-gap:0 !important}.column-gap-lg-1{column-gap:.25rem !important}.column-gap-lg-2{column-gap:.5rem !important}.column-gap-lg-3{column-gap:1rem !important}.column-gap-lg-4{column-gap:1.5rem !important}.column-gap-lg-5{column-gap:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media(min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.object-fit-xl-contain{object-fit:contain !important}.object-fit-xl-cover{object-fit:cover !important}.object-fit-xl-fill{object-fit:fill !important}.object-fit-xl-scale{object-fit:scale-down !important}.object-fit-xl-none{object-fit:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-inline-grid{display:inline-grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.row-gap-xl-0{row-gap:0 !important}.row-gap-xl-1{row-gap:.25rem !important}.row-gap-xl-2{row-gap:.5rem !important}.row-gap-xl-3{row-gap:1rem !important}.row-gap-xl-4{row-gap:1.5rem !important}.row-gap-xl-5{row-gap:3rem !important}.column-gap-xl-0{column-gap:0 !important}.column-gap-xl-1{column-gap:.25rem !important}.column-gap-xl-2{column-gap:.5rem !important}.column-gap-xl-3{column-gap:1rem !important}.column-gap-xl-4{column-gap:1.5rem !important}.column-gap-xl-5{column-gap:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media(min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.object-fit-xxl-contain{object-fit:contain !important}.object-fit-xxl-cover{object-fit:cover !important}.object-fit-xxl-fill{object-fit:fill !important}.object-fit-xxl-scale{object-fit:scale-down !important}.object-fit-xxl-none{object-fit:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-inline-grid{display:inline-grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.row-gap-xxl-0{row-gap:0 !important}.row-gap-xxl-1{row-gap:.25rem !important}.row-gap-xxl-2{row-gap:.5rem !important}.row-gap-xxl-3{row-gap:1rem !important}.row-gap-xxl-4{row-gap:1.5rem !important}.row-gap-xxl-5{row-gap:3rem !important}.column-gap-xxl-0{column-gap:0 !important}.column-gap-xxl-1{column-gap:.25rem !important}.column-gap-xxl-2{column-gap:.5rem !important}.column-gap-xxl-3{column-gap:1rem !important}.column-gap-xxl-4{column-gap:1.5rem !important}.column-gap-xxl-5{column-gap:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}.bg-default{color:#000}.bg-primary{color:#fff}.bg-secondary{color:#fff}.bg-success{color:#fff}.bg-info{color:#000}.bg-warning{color:#000}.bg-danger{color:#fff}.bg-light{color:#000}.bg-dark{color:#fff}@media(min-width: 1200px){.fs-1{font-size:2rem !important}.fs-2{font-size:1.65rem !important}.fs-3{font-size:1.45rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-inline-grid{display:inline-grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}.bg-blue{--bslib-color-bg: #0d6efd;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #0d6efd;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #6f42c1;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #6f42c1;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #d63384;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #d63384;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #dc3545;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #dc3545;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #fd7e14;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ffc107;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ffc107;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #198754;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #198754;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #0dcaf0;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #0dcaf0;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #dee2e6}.bg-default{--bslib-color-bg: #dee2e6;--bslib-color-fg: #000}.text-primary{--bslib-color-fg: #0d6efd}.bg-primary{--bslib-color-bg: #0d6efd;--bslib-color-fg: #ffffff}.text-secondary{--bslib-color-fg: #6c757d}.bg-secondary{--bslib-color-bg: #6c757d;--bslib-color-fg: #ffffff}.text-success{--bslib-color-fg: #198754}.bg-success{--bslib-color-bg: #198754;--bslib-color-fg: #ffffff}.text-info{--bslib-color-fg: #0dcaf0}.bg-info{--bslib-color-bg: #0dcaf0;--bslib-color-fg: #000}.text-warning{--bslib-color-fg: #ffc107}.bg-warning{--bslib-color-bg: #ffc107;--bslib-color-fg: #000}.text-danger{--bslib-color-fg: #dc3545}.bg-danger{--bslib-color-bg: #dc3545;--bslib-color-fg: #ffffff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #212529}.bg-dark{--bslib-color-bg: #212529;--bslib-color-fg: #ffffff}.bg-gradient-blue-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #3148f9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3148f9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #345ce5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #345ce5;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #5d56cd;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #5d56cd;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #6057b3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #6057b3;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #6d74a0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #6d74a0;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #000;--bslib-color-bg: #6e8f9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #6e8f9b;color:#000}.bg-gradient-blue-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #1278b9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #1278b9;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #000;--bslib-color-bg: #1592d4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #1592d4;color:#000}.bg-gradient-blue-cyan{--bslib-color-fg: #000;--bslib-color-bg: #0d93f8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #0d93f8;color:#000}.bg-gradient-indigo-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #4236f6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #4236f6;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #6a24de;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #6a24de;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #931ec6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #931ec6;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #951fad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #951fad;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #a23c99;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #a23c99;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #ffffff;--bslib-color-bg: #a35794;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #a35794;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #4740b3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #4740b3;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #ffffff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #ffffff;--bslib-color-bg: #425af1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #425af1;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #4854d9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #4854d9;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #6b2ed5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #6b2ed5;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #983ca9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #983ca9;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #9b3d8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #9b3d8f;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #a85a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #a85a7c;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #000;--bslib-color-bg: #a97577;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #a97577;color:#000}.bg-gradient-purple-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #4d5e95;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #4d5e95;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #ffffff;--bslib-color-bg: #4f78b0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4f78b0;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #000;--bslib-color-bg: #4878d4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #4878d4;color:#000}.bg-gradient-pink-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #864bb4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #864bb4;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #a925b0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #a925b0;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #ad399c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #ad399c;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #d8346b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #d8346b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #000;--bslib-color-bg: #e65157;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #e65157;color:#000}.bg-gradient-pink-yellow{--bslib-color-fg: #000;--bslib-color-bg: #e66c52;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #e66c52;color:#000}.bg-gradient-pink-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #8a5571;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #8a5571;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #000;--bslib-color-bg: #8d6f8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #8d6f8c;color:#000}.bg-gradient-pink-cyan{--bslib-color-fg: #000;--bslib-color-bg: #866faf;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #866faf;color:#000}.bg-gradient-red-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #894c8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #894c8f;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #ad268a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #ad268a;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #b03a77;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b03a77;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #da345e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #da345e;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #000;--bslib-color-bg: #e95231;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #e95231;color:#000}.bg-gradient-red-yellow{--bslib-color-fg: #000;--bslib-color-bg: #ea6d2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #ea6d2c;color:#000}.bg-gradient-red-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #8e564b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #8e564b;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #000;--bslib-color-bg: #917066;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #917066;color:#000}.bg-gradient-red-cyan{--bslib-color-fg: #000;--bslib-color-bg: #897189;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #897189;color:#000}.bg-gradient-orange-blue{--bslib-color-fg: #000;--bslib-color-bg: #9d7871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #9d7871;color:#000}.bg-gradient-orange-indigo{--bslib-color-fg: #000;--bslib-color-bg: #c1526d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c1526d;color:#000}.bg-gradient-orange-purple{--bslib-color-fg: #000;--bslib-color-bg: #c46659;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #c46659;color:#000}.bg-gradient-orange-pink{--bslib-color-fg: #000;--bslib-color-bg: #ed6041;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #ed6041;color:#000}.bg-gradient-orange-red{--bslib-color-fg: #000;--bslib-color-bg: #f06128;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #f06128;color:#000}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #fe990f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #fe990f;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a2822e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #a2822e;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #a59c48;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a59c48;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #000;--bslib-color-bg: #9d9c6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #9d9c6c;color:#000}.bg-gradient-yellow-blue{--bslib-color-fg: #000;--bslib-color-bg: #9ea069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #9ea069;color:#000}.bg-gradient-yellow-indigo{--bslib-color-fg: #000;--bslib-color-bg: #c27a65;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c27a65;color:#000}.bg-gradient-yellow-purple{--bslib-color-fg: #000;--bslib-color-bg: #c58e51;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #c58e51;color:#000}.bg-gradient-yellow-pink{--bslib-color-fg: #000;--bslib-color-bg: #ef8839;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #ef8839;color:#000}.bg-gradient-yellow-red{--bslib-color-fg: #000;--bslib-color-bg: #f18920;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #f18920;color:#000}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #fea60c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #fea60c;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #000;--bslib-color-bg: #a3aa26;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #a3aa26;color:#000}.bg-gradient-yellow-teal{--bslib-color-fg: #000;--bslib-color-bg: #a6c441;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6c441;color:#000}.bg-gradient-yellow-cyan{--bslib-color-fg: #000;--bslib-color-bg: #9ec564;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #9ec564;color:#000}.bg-gradient-green-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #147d98;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #147d98;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #385793;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #385793;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #3b6b80;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #3b6b80;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #656567;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #656567;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #67664e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #67664e;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #74833a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #74833a;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #000;--bslib-color-bg: #759e35;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #759e35;color:#000}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #1ca16f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #1ca16f;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #000;--bslib-color-bg: #14a292;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #14a292;color:#000}.bg-gradient-teal-blue{--bslib-color-fg: #000;--bslib-color-bg: #18a5c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #18a5c0;color:#000}.bg-gradient-teal-indigo{--bslib-color-fg: #000;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#000}.bg-gradient-teal-purple{--bslib-color-fg: #000;--bslib-color-bg: #4093a8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4093a8;color:#000}.bg-gradient-teal-pink{--bslib-color-fg: #000;--bslib-color-bg: #698d8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #698d8f;color:#000}.bg-gradient-teal-red{--bslib-color-fg: #000;--bslib-color-bg: #6b8e76;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #6b8e76;color:#000}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #78ab63;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #78ab63;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #000;--bslib-color-bg: #79c65d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #79c65d;color:#000}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #1daf7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #1daf7c;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #000;--bslib-color-bg: #18c9bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #18c9bb;color:#000}.bg-gradient-cyan-blue{--bslib-color-fg: #000;--bslib-color-bg: #0da5f5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #0da5f5;color:#000}.bg-gradient-cyan-indigo{--bslib-color-fg: #000;--bslib-color-bg: #3180f1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3180f1;color:#000}.bg-gradient-cyan-purple{--bslib-color-fg: #000;--bslib-color-bg: #3494dd;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #3494dd;color:#000}.bg-gradient-cyan-pink{--bslib-color-fg: #000;--bslib-color-bg: #5d8ec5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #5d8ec5;color:#000}.bg-gradient-cyan-red{--bslib-color-fg: #000;--bslib-color-bg: #608eac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #608eac;color:#000}.bg-gradient-cyan-orange{--bslib-color-fg: #000;--bslib-color-bg: #6dac98;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #6dac98;color:#000}.bg-gradient-cyan-yellow{--bslib-color-fg: #000;--bslib-color-bg: #6ec693;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #6ec693;color:#000}.bg-gradient-cyan-green{--bslib-color-fg: #000;--bslib-color-bg: #12afb2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #12afb2;color:#000}.bg-gradient-cyan-teal{--bslib-color-fg: #000;--bslib-color-bg: #15cacc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #15cacc;color:#000}.tab-content>.tab-pane.html-fill-container{display:none}.tab-content>.active.html-fill-container{display:flex}.tab-content.html-fill-container{padding:0}:root{--bslib-spacer: 1rem;--bslib-mb-spacer: var(--bslib-spacer, 1rem)}.bslib-mb-spacing{margin-bottom:var(--bslib-mb-spacer)}.bslib-gap-spacing{gap:var(--bslib-mb-spacer)}.bslib-gap-spacing>.bslib-mb-spacing,.bslib-gap-spacing>.form-group,.bslib-gap-spacing>p,.bslib-gap-spacing>pre{margin-bottom:0}.html-fill-container>.html-fill-item.bslib-mb-spacing{margin-bottom:0}.bg-blue{--bslib-color-bg: #0d6efd;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-blue{--bslib-color-fg: #0d6efd;color:var(--bslib-color-fg)}.bg-indigo{--bslib-color-bg: #6610f2;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-indigo{--bslib-color-fg: #6610f2;color:var(--bslib-color-fg)}.bg-purple{--bslib-color-bg: #6f42c1;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-purple{--bslib-color-fg: #6f42c1;color:var(--bslib-color-fg)}.bg-pink{--bslib-color-bg: #d63384;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-pink{--bslib-color-fg: #d63384;color:var(--bslib-color-fg)}.bg-red{--bslib-color-bg: #dc3545;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-red{--bslib-color-fg: #dc3545;color:var(--bslib-color-fg)}.bg-orange{--bslib-color-bg: #fd7e14;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-orange{--bslib-color-fg: #fd7e14;color:var(--bslib-color-fg)}.bg-yellow{--bslib-color-bg: #ffc107;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-yellow{--bslib-color-fg: #ffc107;color:var(--bslib-color-fg)}.bg-green{--bslib-color-bg: #198754;--bslib-color-fg: #ffffff;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-green{--bslib-color-fg: #198754;color:var(--bslib-color-fg)}.bg-teal{--bslib-color-bg: #20c997;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-teal{--bslib-color-fg: #20c997;color:var(--bslib-color-fg)}.bg-cyan{--bslib-color-bg: #0dcaf0;--bslib-color-fg: #000;background-color:var(--bslib-color-bg);color:var(--bslib-color-fg)}.text-cyan{--bslib-color-fg: #0dcaf0;color:var(--bslib-color-fg)}.text-default{--bslib-color-fg: #dee2e6}.bg-default{--bslib-color-bg: #dee2e6;--bslib-color-fg: #000}.text-primary{--bslib-color-fg: #0d6efd}.bg-primary{--bslib-color-bg: #0d6efd;--bslib-color-fg: #ffffff}.text-secondary{--bslib-color-fg: #6c757d}.bg-secondary{--bslib-color-bg: #6c757d;--bslib-color-fg: #ffffff}.text-success{--bslib-color-fg: #198754}.bg-success{--bslib-color-bg: #198754;--bslib-color-fg: #ffffff}.text-info{--bslib-color-fg: #0dcaf0}.bg-info{--bslib-color-bg: #0dcaf0;--bslib-color-fg: #000}.text-warning{--bslib-color-fg: #ffc107}.bg-warning{--bslib-color-bg: #ffc107;--bslib-color-fg: #000}.text-danger{--bslib-color-fg: #dc3545}.bg-danger{--bslib-color-bg: #dc3545;--bslib-color-fg: #ffffff}.text-light{--bslib-color-fg: #f8f9fa}.bg-light{--bslib-color-bg: #f8f9fa;--bslib-color-fg: #000}.text-dark{--bslib-color-fg: #212529}.bg-dark{--bslib-color-bg: #212529;--bslib-color-fg: #ffffff}.bg-gradient-blue-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #3148f9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3148f9;color:#fff}.bg-gradient-blue-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #345ce5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #345ce5;color:#fff}.bg-gradient-blue-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #5d56cd;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #5d56cd;color:#fff}.bg-gradient-blue-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #6057b3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #6057b3;color:#fff}.bg-gradient-blue-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #6d74a0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #6d74a0;color:#fff}.bg-gradient-blue-yellow{--bslib-color-fg: #000;--bslib-color-bg: #6e8f9b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #6e8f9b;color:#000}.bg-gradient-blue-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #1278b9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #1278b9;color:#fff}.bg-gradient-blue-teal{--bslib-color-fg: #000;--bslib-color-bg: #1592d4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #1592d4;color:#000}.bg-gradient-blue-cyan{--bslib-color-fg: #000;--bslib-color-bg: #0d93f8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0d6efd var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #0d93f8;color:#000}.bg-gradient-indigo-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #4236f6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #4236f6;color:#fff}.bg-gradient-indigo-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #6a24de;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #6a24de;color:#fff}.bg-gradient-indigo-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #931ec6;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #931ec6;color:#fff}.bg-gradient-indigo-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #951fad;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #951fad;color:#fff}.bg-gradient-indigo-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #a23c99;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #a23c99;color:#fff}.bg-gradient-indigo-yellow{--bslib-color-fg: #ffffff;--bslib-color-bg: #a35794;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #a35794;color:#fff}.bg-gradient-indigo-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #4740b3;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #4740b3;color:#fff}.bg-gradient-indigo-teal{--bslib-color-fg: #ffffff;--bslib-color-bg: #4a5ace;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4a5ace;color:#fff}.bg-gradient-indigo-cyan{--bslib-color-fg: #ffffff;--bslib-color-bg: #425af1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6610f2 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #425af1;color:#fff}.bg-gradient-purple-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #4854d9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #4854d9;color:#fff}.bg-gradient-purple-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #6b2ed5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #6b2ed5;color:#fff}.bg-gradient-purple-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #983ca9;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #983ca9;color:#fff}.bg-gradient-purple-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #9b3d8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #9b3d8f;color:#fff}.bg-gradient-purple-orange{--bslib-color-fg: #ffffff;--bslib-color-bg: #a85a7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #a85a7c;color:#fff}.bg-gradient-purple-yellow{--bslib-color-fg: #000;--bslib-color-bg: #a97577;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #a97577;color:#000}.bg-gradient-purple-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #4d5e95;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #4d5e95;color:#fff}.bg-gradient-purple-teal{--bslib-color-fg: #ffffff;--bslib-color-bg: #4f78b0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #4f78b0;color:#fff}.bg-gradient-purple-cyan{--bslib-color-fg: #000;--bslib-color-bg: #4878d4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #6f42c1 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #4878d4;color:#000}.bg-gradient-pink-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #864bb4;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #864bb4;color:#fff}.bg-gradient-pink-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #a925b0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #a925b0;color:#fff}.bg-gradient-pink-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #ad399c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #ad399c;color:#fff}.bg-gradient-pink-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #d8346b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #d8346b;color:#fff}.bg-gradient-pink-orange{--bslib-color-fg: #000;--bslib-color-bg: #e65157;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #e65157;color:#000}.bg-gradient-pink-yellow{--bslib-color-fg: #000;--bslib-color-bg: #e66c52;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #e66c52;color:#000}.bg-gradient-pink-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #8a5571;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #8a5571;color:#fff}.bg-gradient-pink-teal{--bslib-color-fg: #000;--bslib-color-bg: #8d6f8c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #8d6f8c;color:#000}.bg-gradient-pink-cyan{--bslib-color-fg: #000;--bslib-color-bg: #866faf;background:linear-gradient(var(--bg-gradient-deg, 140deg), #d63384 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #866faf;color:#000}.bg-gradient-red-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #894c8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #894c8f;color:#fff}.bg-gradient-red-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #ad268a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #ad268a;color:#fff}.bg-gradient-red-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #b03a77;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #b03a77;color:#fff}.bg-gradient-red-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #da345e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #da345e;color:#fff}.bg-gradient-red-orange{--bslib-color-fg: #000;--bslib-color-bg: #e95231;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #e95231;color:#000}.bg-gradient-red-yellow{--bslib-color-fg: #000;--bslib-color-bg: #ea6d2c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #ea6d2c;color:#000}.bg-gradient-red-green{--bslib-color-fg: #ffffff;--bslib-color-bg: #8e564b;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #8e564b;color:#fff}.bg-gradient-red-teal{--bslib-color-fg: #000;--bslib-color-bg: #917066;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #917066;color:#000}.bg-gradient-red-cyan{--bslib-color-fg: #000;--bslib-color-bg: #897189;background:linear-gradient(var(--bg-gradient-deg, 140deg), #dc3545 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #897189;color:#000}.bg-gradient-orange-blue{--bslib-color-fg: #000;--bslib-color-bg: #9d7871;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #9d7871;color:#000}.bg-gradient-orange-indigo{--bslib-color-fg: #000;--bslib-color-bg: #c1526d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c1526d;color:#000}.bg-gradient-orange-purple{--bslib-color-fg: #000;--bslib-color-bg: #c46659;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #c46659;color:#000}.bg-gradient-orange-pink{--bslib-color-fg: #000;--bslib-color-bg: #ed6041;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #ed6041;color:#000}.bg-gradient-orange-red{--bslib-color-fg: #000;--bslib-color-bg: #f06128;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #f06128;color:#000}.bg-gradient-orange-yellow{--bslib-color-fg: #000;--bslib-color-bg: #fe990f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #fe990f;color:#000}.bg-gradient-orange-green{--bslib-color-fg: #000;--bslib-color-bg: #a2822e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #a2822e;color:#000}.bg-gradient-orange-teal{--bslib-color-fg: #000;--bslib-color-bg: #a59c48;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a59c48;color:#000}.bg-gradient-orange-cyan{--bslib-color-fg: #000;--bslib-color-bg: #9d9c6c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #fd7e14 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #9d9c6c;color:#000}.bg-gradient-yellow-blue{--bslib-color-fg: #000;--bslib-color-bg: #9ea069;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #9ea069;color:#000}.bg-gradient-yellow-indigo{--bslib-color-fg: #000;--bslib-color-bg: #c27a65;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #c27a65;color:#000}.bg-gradient-yellow-purple{--bslib-color-fg: #000;--bslib-color-bg: #c58e51;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #c58e51;color:#000}.bg-gradient-yellow-pink{--bslib-color-fg: #000;--bslib-color-bg: #ef8839;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #ef8839;color:#000}.bg-gradient-yellow-red{--bslib-color-fg: #000;--bslib-color-bg: #f18920;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #f18920;color:#000}.bg-gradient-yellow-orange{--bslib-color-fg: #000;--bslib-color-bg: #fea60c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #fea60c;color:#000}.bg-gradient-yellow-green{--bslib-color-fg: #000;--bslib-color-bg: #a3aa26;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #a3aa26;color:#000}.bg-gradient-yellow-teal{--bslib-color-fg: #000;--bslib-color-bg: #a6c441;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #a6c441;color:#000}.bg-gradient-yellow-cyan{--bslib-color-fg: #000;--bslib-color-bg: #9ec564;background:linear-gradient(var(--bg-gradient-deg, 140deg), #ffc107 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #9ec564;color:#000}.bg-gradient-green-blue{--bslib-color-fg: #ffffff;--bslib-color-bg: #147d98;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #147d98;color:#fff}.bg-gradient-green-indigo{--bslib-color-fg: #ffffff;--bslib-color-bg: #385793;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #385793;color:#fff}.bg-gradient-green-purple{--bslib-color-fg: #ffffff;--bslib-color-bg: #3b6b80;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #3b6b80;color:#fff}.bg-gradient-green-pink{--bslib-color-fg: #ffffff;--bslib-color-bg: #656567;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #656567;color:#fff}.bg-gradient-green-red{--bslib-color-fg: #ffffff;--bslib-color-bg: #67664e;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #67664e;color:#fff}.bg-gradient-green-orange{--bslib-color-fg: #000;--bslib-color-bg: #74833a;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #74833a;color:#000}.bg-gradient-green-yellow{--bslib-color-fg: #000;--bslib-color-bg: #759e35;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #759e35;color:#000}.bg-gradient-green-teal{--bslib-color-fg: #000;--bslib-color-bg: #1ca16f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #1ca16f;color:#000}.bg-gradient-green-cyan{--bslib-color-fg: #000;--bslib-color-bg: #14a292;background:linear-gradient(var(--bg-gradient-deg, 140deg), #198754 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #14a292;color:#000}.bg-gradient-teal-blue{--bslib-color-fg: #000;--bslib-color-bg: #18a5c0;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #18a5c0;color:#000}.bg-gradient-teal-indigo{--bslib-color-fg: #000;--bslib-color-bg: #3c7fbb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3c7fbb;color:#000}.bg-gradient-teal-purple{--bslib-color-fg: #000;--bslib-color-bg: #4093a8;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #4093a8;color:#000}.bg-gradient-teal-pink{--bslib-color-fg: #000;--bslib-color-bg: #698d8f;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #698d8f;color:#000}.bg-gradient-teal-red{--bslib-color-fg: #000;--bslib-color-bg: #6b8e76;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #6b8e76;color:#000}.bg-gradient-teal-orange{--bslib-color-fg: #000;--bslib-color-bg: #78ab63;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #78ab63;color:#000}.bg-gradient-teal-yellow{--bslib-color-fg: #000;--bslib-color-bg: #79c65d;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #79c65d;color:#000}.bg-gradient-teal-green{--bslib-color-fg: #000;--bslib-color-bg: #1daf7c;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #1daf7c;color:#000}.bg-gradient-teal-cyan{--bslib-color-fg: #000;--bslib-color-bg: #18c9bb;background:linear-gradient(var(--bg-gradient-deg, 140deg), #20c997 var(--bg-gradient-start, 36%), #0dcaf0 var(--bg-gradient-end, 180%)) #18c9bb;color:#000}.bg-gradient-cyan-blue{--bslib-color-fg: #000;--bslib-color-bg: #0da5f5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #0d6efd var(--bg-gradient-end, 180%)) #0da5f5;color:#000}.bg-gradient-cyan-indigo{--bslib-color-fg: #000;--bslib-color-bg: #3180f1;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #6610f2 var(--bg-gradient-end, 180%)) #3180f1;color:#000}.bg-gradient-cyan-purple{--bslib-color-fg: #000;--bslib-color-bg: #3494dd;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #6f42c1 var(--bg-gradient-end, 180%)) #3494dd;color:#000}.bg-gradient-cyan-pink{--bslib-color-fg: #000;--bslib-color-bg: #5d8ec5;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #d63384 var(--bg-gradient-end, 180%)) #5d8ec5;color:#000}.bg-gradient-cyan-red{--bslib-color-fg: #000;--bslib-color-bg: #608eac;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #dc3545 var(--bg-gradient-end, 180%)) #608eac;color:#000}.bg-gradient-cyan-orange{--bslib-color-fg: #000;--bslib-color-bg: #6dac98;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #fd7e14 var(--bg-gradient-end, 180%)) #6dac98;color:#000}.bg-gradient-cyan-yellow{--bslib-color-fg: #000;--bslib-color-bg: #6ec693;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #ffc107 var(--bg-gradient-end, 180%)) #6ec693;color:#000}.bg-gradient-cyan-green{--bslib-color-fg: #000;--bslib-color-bg: #12afb2;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #198754 var(--bg-gradient-end, 180%)) #12afb2;color:#000}.bg-gradient-cyan-teal{--bslib-color-fg: #000;--bslib-color-bg: #15cacc;background:linear-gradient(var(--bg-gradient-deg, 140deg), #0dcaf0 var(--bg-gradient-start, 36%), #20c997 var(--bg-gradient-end, 180%)) #15cacc;color:#000}:root{--bslib-value-box-shadow: none;--bslib-value-box-border-width-auto-yes: var(--bslib-value-box-border-width-baseline);--bslib-value-box-border-width-auto-no: 0;--bslib-value-box-border-width-baseline: 1px}.bslib-value-box{border-width:var(--bslib-value-box-border-width-auto-no, var(--bslib-value-box-border-width-baseline));container-name:bslib-value-box;container-type:inline-size}.bslib-value-box.card{box-shadow:var(--bslib-value-box-shadow)}.bslib-value-box.border-auto{border-width:var(--bslib-value-box-border-width-auto-yes, var(--bslib-value-box-border-width-baseline))}.bslib-value-box.default{--bslib-value-box-bg-default: var(--bs-card-bg, #ffffff);--bslib-value-box-border-color-default: var(--bs-card-border-color, rgba(0, 0, 0, 0.175));color:var(--bslib-value-box-color);background-color:var(--bslib-value-box-bg, var(--bslib-value-box-bg-default));border-color:var(--bslib-value-box-border-color, var(--bslib-value-box-border-color-default))}.bslib-value-box .value-box-grid{display:grid;grid-template-areas:"left right";align-items:center;overflow:hidden}.bslib-value-box .value-box-showcase{height:100%;max-height:var(---bslib-value-box-showcase-max-h, 100%)}.bslib-value-box .value-box-showcase,.bslib-value-box .value-box-showcase>.html-fill-item{width:100%}.bslib-value-box[data-full-screen=true] .value-box-showcase{max-height:var(---bslib-value-box-showcase-max-h-fs, 100%)}@media screen and (min-width: 575.98px){@container bslib-value-box (max-width: 300px){.bslib-value-box:not(.showcase-bottom) .value-box-grid{grid-template-columns:1fr !important;grid-template-rows:auto auto;grid-template-areas:"top" "bottom"}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-showcase{grid-area:top !important}.bslib-value-box:not(.showcase-bottom) .value-box-grid .value-box-area{grid-area:bottom !important;justify-content:end}}}.bslib-value-box .value-box-area{justify-content:center;padding:1.5rem 1rem;font-size:.9rem;font-weight:500}.bslib-value-box .value-box-area *{margin-bottom:0;margin-top:0}.bslib-value-box .value-box-title{font-size:1rem;margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.bslib-value-box .value-box-title:empty::after{content:" "}.bslib-value-box .value-box-value{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}@media(min-width: 1200px){.bslib-value-box .value-box-value{font-size:1.65rem}}.bslib-value-box .value-box-value:empty::after{content:" "}.bslib-value-box .value-box-showcase{align-items:center;justify-content:center;margin-top:auto;margin-bottom:auto;padding:1rem}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{opacity:.85;min-width:50px;max-width:125%}.bslib-value-box .value-box-showcase .bi,.bslib-value-box .value-box-showcase .fa,.bslib-value-box .value-box-showcase .fab,.bslib-value-box .value-box-showcase .fas,.bslib-value-box .value-box-showcase .far{font-size:4rem}.bslib-value-box.showcase-top-right .value-box-grid{grid-template-columns:1fr var(---bslib-value-box-showcase-w, 50%)}.bslib-value-box.showcase-top-right .value-box-grid .value-box-showcase{grid-area:right;margin-left:auto;align-self:start;align-items:end;padding-left:0;padding-bottom:0}.bslib-value-box.showcase-top-right .value-box-grid .value-box-area{grid-area:left;align-self:end}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid{grid-template-columns:auto var(---bslib-value-box-showcase-w-fs, 1fr)}.bslib-value-box.showcase-top-right[data-full-screen=true] .value-box-grid>div{align-self:center}.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-showcase{margin-top:0}@container bslib-value-box (max-width: 300px){.bslib-value-box.showcase-top-right:not([data-full-screen=true]) .value-box-grid .value-box-showcase{padding-left:1rem}}.bslib-value-box.showcase-left-center .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w, 30%) auto}.bslib-value-box.showcase-left-center[data-full-screen=true] .value-box-grid{grid-template-columns:var(---bslib-value-box-showcase-w-fs, 1fr) auto}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-showcase{grid-area:left}.bslib-value-box.showcase-left-center:not([data-fill-screen=true]) .value-box-grid .value-box-area{grid-area:right}.bslib-value-box.showcase-bottom .value-box-grid{grid-template-columns:1fr;grid-template-rows:1fr var(---bslib-value-box-showcase-h, auto);grid-template-areas:"top" "bottom";overflow:hidden}.bslib-value-box.showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.bslib-value-box.showcase-bottom .value-box-grid .value-box-area{grid-area:top}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid{grid-template-rows:1fr var(---bslib-value-box-showcase-h-fs, 2fr)}.bslib-value-box.showcase-bottom[data-full-screen=true] .value-box-grid .value-box-showcase{padding:1rem}[data-bs-theme=dark] .bslib-value-box{--bslib-value-box-shadow: 0 0.5rem 1rem rgb(0 0 0 / 50%)}@media(min-width: 576px){.nav:not(.nav-hidden){display:flex !important;display:-webkit-flex !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column){float:none !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.bslib-nav-spacer{margin-left:auto !important}.nav:not(.nav-hidden):not(.nav-stacked):not(.flex-column)>.form-inline{margin-top:auto;margin-bottom:auto}.nav:not(.nav-hidden).nav-stacked{flex-direction:column;-webkit-flex-direction:column;height:100%}.nav:not(.nav-hidden).nav-stacked>.bslib-nav-spacer{margin-top:auto !important}}.bslib-card{overflow:auto}.bslib-card .card-body+.card-body{padding-top:0}.bslib-card .card-body{overflow:auto}.bslib-card .card-body p{margin-top:0}.bslib-card .card-body p:last-child{margin-bottom:0}.bslib-card .card-body{max-height:var(--bslib-card-body-max-height, none)}.bslib-card[data-full-screen=true]>.card-body{max-height:var(--bslib-card-body-max-height-full-screen, none)}.bslib-card .card-header .form-group{margin-bottom:0}.bslib-card .card-header .selectize-control{margin-bottom:0}.bslib-card .card-header .selectize-control .item{margin-right:1.15rem}.bslib-card .card-footer{margin-top:auto}.bslib-card .bslib-navs-card-title{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center}.bslib-card .bslib-navs-card-title .nav{margin-left:auto}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border=true]){border:none}.bslib-card .bslib-sidebar-layout:not([data-bslib-sidebar-border-radius=true]){border-top-left-radius:0;border-top-right-radius:0}[data-full-screen=true]{position:fixed;inset:3.5rem 1rem 1rem;height:auto !important;max-height:none !important;width:auto !important;z-index:1070}.bslib-full-screen-enter{display:none;position:absolute;bottom:var(--bslib-full-screen-enter-bottom, 0.2rem);right:var(--bslib-full-screen-enter-right, 0);top:var(--bslib-full-screen-enter-top);left:var(--bslib-full-screen-enter-left);color:var(--bslib-color-fg, var(--bs-card-color));background-color:var(--bslib-color-bg, var(--bs-card-bg, var(--bs-body-bg)));border:var(--bs-card-border-width) solid var(--bslib-color-fg, var(--bs-card-border-color));box-shadow:0 2px 4px rgba(0,0,0,.15);margin:.2rem .4rem;padding:.55rem !important;font-size:.8rem;cursor:pointer;opacity:.7;z-index:1070}.bslib-full-screen-enter:hover{opacity:1}.card[data-full-screen=false]:hover>*>.bslib-full-screen-enter{display:block}.bslib-has-full-screen .card:hover>*>.bslib-full-screen-enter{display:none}@media(max-width: 575.98px){.bslib-full-screen-enter{display:none !important}}.bslib-full-screen-exit{position:relative;top:1.35rem;font-size:.9rem;cursor:pointer;text-decoration:none;display:flex;float:right;margin-right:2.15rem;align-items:center;color:rgba(var(--bs-body-bg-rgb), 0.8)}.bslib-full-screen-exit:hover{color:rgba(var(--bs-body-bg-rgb), 1)}.bslib-full-screen-exit svg{margin-left:.5rem;font-size:1.5rem}#bslib-full-screen-overlay{position:fixed;inset:0;background-color:rgba(var(--bs-body-color-rgb), 0.6);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);z-index:1069;animation:bslib-full-screen-overlay-enter 400ms cubic-bezier(0.6, 0.02, 0.65, 1) forwards}@keyframes bslib-full-screen-overlay-enter{0%{opacity:0}100%{opacity:1}}.bslib-grid{display:grid !important;gap:var(--bslib-spacer, 1rem);height:var(--bslib-grid-height)}.bslib-grid.grid{grid-template-columns:repeat(var(--bs-columns, 12), minmax(0, 1fr));grid-template-rows:unset;grid-auto-rows:var(--bslib-grid--row-heights);--bslib-grid--row-heights--xs: unset;--bslib-grid--row-heights--sm: unset;--bslib-grid--row-heights--md: unset;--bslib-grid--row-heights--lg: unset;--bslib-grid--row-heights--xl: unset;--bslib-grid--row-heights--xxl: unset}.bslib-grid.grid.bslib-grid--row-heights--xs{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xs)}@media(min-width: 576px){.bslib-grid.grid.bslib-grid--row-heights--sm{--bslib-grid--row-heights: var(--bslib-grid--row-heights--sm)}}@media(min-width: 768px){.bslib-grid.grid.bslib-grid--row-heights--md{--bslib-grid--row-heights: var(--bslib-grid--row-heights--md)}}@media(min-width: 992px){.bslib-grid.grid.bslib-grid--row-heights--lg{--bslib-grid--row-heights: var(--bslib-grid--row-heights--lg)}}@media(min-width: 1200px){.bslib-grid.grid.bslib-grid--row-heights--xl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xl)}}@media(min-width: 1400px){.bslib-grid.grid.bslib-grid--row-heights--xxl{--bslib-grid--row-heights: var(--bslib-grid--row-heights--xxl)}}.bslib-grid>*>.shiny-input-container{width:100%}.bslib-grid-item{grid-column:auto/span 1}@media(max-width: 767.98px){.bslib-grid-item{grid-column:1/-1}}@media(max-width: 575.98px){.bslib-grid{grid-template-columns:1fr !important;height:var(--bslib-grid-height-mobile)}.bslib-grid.grid{height:unset !important;grid-auto-rows:var(--bslib-grid--row-heights--xs, auto)}}.accordion .accordion-header{font-size:calc(1.29rem + 0.48vw);margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color);margin-bottom:0}@media(min-width: 1200px){.accordion .accordion-header{font-size:1.65rem}}.accordion .accordion-icon:not(:empty){margin-right:.75rem;display:flex}.accordion .accordion-button:not(.collapsed){box-shadow:none}.accordion .accordion-button:not(.collapsed):focus{box-shadow:var(--bs-accordion-btn-focus-box-shadow)}.navbar+.container-fluid:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-sm:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-md:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-lg:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xl:has(>.tab-content>.tab-pane.active.html-fill-container),.navbar+.container-xxl:has(>.tab-content>.tab-pane.active.html-fill-container){padding-left:0;padding-right:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container,.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container{padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container:has(>.bslib-sidebar-layout:only-child){padding:0}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border=true]){border-left:none;border-right:none;border-bottom:none}.navbar+.container-fluid>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-sm>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-md>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-lg>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]),.navbar+.container-xxl>.tab-content>.tab-pane.active.html-fill-container>.bslib-sidebar-layout:only-child:not([data-bslib-sidebar-border-radius=true]){border-radius:0}.navbar+div>.bslib-sidebar-layout{border-top:var(--bslib-sidebar-border)}html{height:100%}.bslib-page-fill{width:100%;height:100%;margin:0;padding:var(--bslib-spacer, 1rem);gap:var(--bslib-spacer, 1rem)}@media(max-width: 575.98px){.bslib-page-fill{height:var(--bslib-page-fill-mobile-height, auto)}}:root{--bslib-page-sidebar-title-bg: #517699;--bslib-page-sidebar-title-color: #ffffff}.bslib-page-title{background-color:var(--bslib-page-sidebar-title-bg);color:var(--bslib-page-sidebar-title-color);font-size:1.25rem;font-weight:300;padding:var(--bslib-spacer, 1rem);padding-left:1.5rem;margin-bottom:0;border-bottom:1px solid #dee2e6}.bslib-sidebar-layout{--bslib-sidebar-transition-duration: 500ms;--bslib-sidebar-transition-easing-x: cubic-bezier(0.8, 0.78, 0.22, 1.07);--bslib-sidebar-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-border-radius: var(--bs-border-radius);--bslib-sidebar-vert-border: var(--bs-card-border-width, 1px) solid var(--bs-card-border-color, rgba(0, 0, 0, 0.175));--bslib-sidebar-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.05);--bslib-sidebar-fg: var(--bs-emphasis-color, black);--bslib-sidebar-main-fg: var(--bs-card-color, var(--bs-body-color));--bslib-sidebar-main-bg: var(--bs-card-bg, var(--bs-body-bg));--bslib-sidebar-toggle-bg: rgba(var(--bs-emphasis-color-rgb, 0, 0, 0), 0.1);--bslib-sidebar-padding: calc(var(--bslib-spacer) * 1.5);--bslib-sidebar-icon-size: var(--bslib-spacer, 1rem);--bslib-sidebar-icon-button-size: calc(var(--bslib-sidebar-icon-size, 1rem) * 2);--bslib-sidebar-padding-icon: calc(var(--bslib-sidebar-icon-button-size, 2rem) * 1.5);--bslib-collapse-toggle-border-radius: var(--bs-border-radius, 0.25rem);--bslib-collapse-toggle-transform: 0deg;--bslib-sidebar-toggle-transition-easing: cubic-bezier(1, 0, 0, 1);--bslib-collapse-toggle-right-transform: 180deg;--bslib-sidebar-column-main: minmax(0, 1fr);display:grid !important;grid-template-columns:min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px)) var(--bslib-sidebar-column-main);position:relative;transition:grid-template-columns ease-in-out var(--bslib-sidebar-transition-duration);border:var(--bslib-sidebar-border);border-radius:var(--bslib-sidebar-border-radius)}@media(prefers-reduced-motion: reduce){.bslib-sidebar-layout{transition:none}}.bslib-sidebar-layout[data-bslib-sidebar-border=false]{border:none}.bslib-sidebar-layout[data-bslib-sidebar-border-radius=false]{border-radius:initial}.bslib-sidebar-layout>.main,.bslib-sidebar-layout>.sidebar{grid-row:1/2;border-radius:inherit;overflow:auto}.bslib-sidebar-layout>.main{grid-column:2/3;border-top-left-radius:0;border-bottom-left-radius:0;padding:var(--bslib-sidebar-padding);transition:padding var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration);color:var(--bslib-sidebar-main-fg);background-color:var(--bslib-sidebar-main-bg)}.bslib-sidebar-layout>.sidebar{grid-column:1/2;width:100%;height:100%;border-right:var(--bslib-sidebar-vert-border);border-top-right-radius:0;border-bottom-right-radius:0;color:var(--bslib-sidebar-fg);background-color:var(--bslib-sidebar-bg);backdrop-filter:blur(5px)}.bslib-sidebar-layout>.sidebar>.sidebar-content{display:flex;flex-direction:column;gap:var(--bslib-spacer, 1rem);padding:var(--bslib-sidebar-padding);padding-top:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout>.sidebar>.sidebar-content>:last-child:not(.sidebar-title){margin-bottom:0}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion{margin-left:calc(-1*var(--bslib-sidebar-padding));margin-right:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:last-child{margin-bottom:calc(-1*var(--bslib-sidebar-padding))}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child){margin-bottom:1rem}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion .accordion-body{display:flex;flex-direction:column}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:first-child) .accordion-item:first-child{border-top:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content>.accordion:not(:last-child) .accordion-item:last-child{border-bottom:var(--bs-accordion-border-width) solid var(--bs-accordion-border-color)}.bslib-sidebar-layout>.sidebar>.sidebar-content.has-accordion>.sidebar-title{border-bottom:none;padding-bottom:0}.bslib-sidebar-layout>.sidebar .shiny-input-container{width:100%}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar>.sidebar-content{padding-top:var(--bslib-sidebar-padding)}.bslib-sidebar-layout>.collapse-toggle{grid-row:1/2;grid-column:1/2;display:inline-flex;align-items:center;position:absolute;right:calc(var(--bslib-sidebar-icon-size));top:calc(var(--bslib-sidebar-icon-size, 1rem)/2);border:none;border-radius:var(--bslib-collapse-toggle-border-radius);height:var(--bslib-sidebar-icon-button-size, 2rem);width:var(--bslib-sidebar-icon-button-size, 2rem);display:flex;align-items:center;justify-content:center;padding:0;color:var(--bslib-sidebar-fg);background-color:unset;transition:color var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),top var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),right var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration),left var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover{background-color:var(--bslib-sidebar-toggle-bg)}.bslib-sidebar-layout>.collapse-toggle>.collapse-icon{opacity:.8;width:var(--bslib-sidebar-icon-size);height:var(--bslib-sidebar-icon-size);transform:rotateY(var(--bslib-collapse-toggle-transform));transition:transform var(--bslib-sidebar-toggle-transition-easing) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout>.collapse-toggle:hover>.collapse-icon{opacity:1}.bslib-sidebar-layout .sidebar-title{font-size:1.25rem;line-height:1.25;margin-top:0;margin-bottom:1rem;padding-bottom:1rem;border-bottom:var(--bslib-sidebar-border)}.bslib-sidebar-layout.sidebar-right{grid-template-columns:var(--bslib-sidebar-column-main) min(100% - var(--bslib-sidebar-icon-size),var(--bslib-sidebar-width, 250px))}.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/2;border-top-right-radius:0;border-bottom-right-radius:0;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.bslib-sidebar-layout.sidebar-right>.sidebar{grid-column:2/3;border-right:none;border-left:var(--bslib-sidebar-vert-border);border-top-left-radius:0;border-bottom-left-radius:0}.bslib-sidebar-layout.sidebar-right>.collapse-toggle{grid-column:2/3;left:var(--bslib-sidebar-icon-size);right:unset;border:var(--bslib-collapse-toggle-border)}.bslib-sidebar-layout.sidebar-right>.collapse-toggle>.collapse-icon{transform:rotateY(var(--bslib-collapse-toggle-right-transform))}.bslib-sidebar-layout.sidebar-collapsed{--bslib-collapse-toggle-transform: 180deg;--bslib-collapse-toggle-right-transform: 0deg;--bslib-sidebar-vert-border: none;grid-template-columns:0 minmax(0, 1fr)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right{grid-template-columns:minmax(0, 1fr) 0}.bslib-sidebar-layout.sidebar-collapsed:not(.transitioning)>.sidebar>*{display:none}.bslib-sidebar-layout.sidebar-collapsed>.main{border-radius:inherit}.bslib-sidebar-layout.sidebar-collapsed:not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle{color:var(--bslib-sidebar-main-fg);top:calc(var(--bslib-sidebar-overlap-counter, 0)*(var(--bslib-sidebar-icon-size) + var(--bslib-sidebar-padding)) + var(--bslib-sidebar-icon-size, 1rem)/2);right:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px))}.bslib-sidebar-layout.sidebar-collapsed.sidebar-right>.collapse-toggle{left:calc(-2.5*var(--bslib-sidebar-icon-size) - var(--bs-card-border-width, 1px));right:unset}@media(min-width: 576px){.bslib-sidebar-layout.transitioning>.sidebar>.sidebar-content{display:none}}@media(max-width: 575.98px){.bslib-sidebar-layout[data-bslib-sidebar-open=desktop]{--bslib-sidebar-js-init-collapsed: true}.bslib-sidebar-layout>.sidebar,.bslib-sidebar-layout.sidebar-right>.sidebar{border:none}.bslib-sidebar-layout>.main,.bslib-sidebar-layout.sidebar-right>.main{grid-column:1/3}.bslib-sidebar-layout[data-bslib-sidebar-open=always]{display:block !important}.bslib-sidebar-layout[data-bslib-sidebar-open=always]>.sidebar{max-height:var(--bslib-sidebar-max-height-mobile);overflow-y:auto;border-top:var(--bslib-sidebar-vert-border)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]){grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.sidebar{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-collapsed)>.collapse-toggle{z-index:1}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed{grid-template-columns:0 100%}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed.sidebar-right{grid-template-columns:100% 0}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]):not(.sidebar-right)>.main{padding-left:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-right>.main{padding-right:var(--bslib-sidebar-padding-icon)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always])>.main{opacity:0;transition:opacity var(--bslib-sidebar-transition-easing-x) var(--bslib-sidebar-transition-duration)}.bslib-sidebar-layout:not([data-bslib-sidebar-open=always]).sidebar-collapsed>.main{opacity:1}}.html-fill-container{display:flex;flex-direction:column;min-height:0;min-width:0}.html-fill-container>.html-fill-item{flex:1 1 auto;min-height:0;min-width:0}.html-fill-container>:not(.html-fill-item){flex:0 0 auto}.sidebar-item .chapter-number{color:#212529}.quarto-container{min-height:calc(100vh - 132px)}body.hypothesis-enabled #quarto-header{margin-right:16px}footer.footer .nav-footer,#quarto-header>nav{padding-left:1em;padding-right:1em}footer.footer div.nav-footer p:first-child{margin-top:0}footer.footer div.nav-footer p:last-child{margin-bottom:0}#quarto-content>*{padding-top:14px}#quarto-content>#quarto-sidebar-glass{padding-top:0px}@media(max-width: 991.98px){#quarto-content>*{padding-top:0}#quarto-content .subtitle{padding-top:14px}#quarto-content section:first-of-type h2:first-of-type,#quarto-content section:first-of-type .h2:first-of-type{margin-top:1rem}}.headroom-target,header.headroom{will-change:transform;transition:position 200ms linear;transition:all 200ms linear}header.headroom--pinned{transform:translateY(0%)}header.headroom--unpinned{transform:translateY(-100%)}.navbar-container{width:100%}.navbar-brand{overflow:hidden;text-overflow:ellipsis}.navbar-brand-container{max-width:calc(100% - 115px);min-width:0;display:flex;align-items:center}@media(min-width: 992px){.navbar-brand-container{margin-right:1em}}.navbar-brand.navbar-brand-logo{margin-right:4px;display:inline-flex}.navbar-toggler{flex-basis:content;flex-shrink:0}.navbar .navbar-brand-container{order:2}.navbar .navbar-toggler{order:1}.navbar .navbar-container>.navbar-nav{order:20}.navbar .navbar-container>.navbar-brand-container{margin-left:0 !important;margin-right:0 !important}.navbar .navbar-collapse{order:20}.navbar #quarto-search{order:4;margin-left:auto}.navbar .navbar-toggler{margin-right:.5em}.navbar-logo{max-height:24px;width:auto;padding-right:4px}nav .nav-item:not(.compact){padding-top:1px}nav .nav-link i,nav .dropdown-item i{padding-right:1px}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.6rem;padding-right:.6rem}nav .nav-item.compact .nav-link{padding-left:.5rem;padding-right:.5rem;font-size:1.1rem}.navbar .quarto-navbar-tools{order:3}.navbar .quarto-navbar-tools div.dropdown{display:inline-block}.navbar .quarto-navbar-tools .quarto-navigation-tool{color:#fdfefe}.navbar .quarto-navbar-tools .quarto-navigation-tool:hover{color:#fcfcff}.navbar-nav .dropdown-menu{min-width:220px;font-size:.9rem}.navbar .navbar-nav .nav-link.dropdown-toggle::after{opacity:.75;vertical-align:.175em}.navbar ul.dropdown-menu{padding-top:0;padding-bottom:0}.navbar .dropdown-header{text-transform:uppercase;font-size:.8rem;padding:0 .5rem}.navbar .dropdown-item{padding:.4rem .5rem}.navbar .dropdown-item>i.bi{margin-left:.1rem;margin-right:.25em}.sidebar #quarto-search{margin-top:-1px}.sidebar #quarto-search svg.aa-SubmitIcon{width:16px;height:16px}.sidebar-navigation a{color:inherit}.sidebar-title{margin-top:.25rem;padding-bottom:.5rem;font-size:1.3rem;line-height:1.6rem;visibility:visible}.sidebar-title>a{font-size:inherit;text-decoration:none}.sidebar-title .sidebar-tools-main{margin-top:-6px}@media(max-width: 991.98px){#quarto-sidebar div.sidebar-header{padding-top:.2em}}.sidebar-header-stacked .sidebar-title{margin-top:.6rem}.sidebar-logo{max-width:90%;padding-bottom:.5rem}.sidebar-logo-link{text-decoration:none}.sidebar-navigation li a{text-decoration:none}.sidebar-navigation .quarto-navigation-tool{opacity:.7;font-size:.875rem}#quarto-sidebar>nav>.sidebar-tools-main{margin-left:14px}.sidebar-tools-main{display:inline-flex;margin-left:0px;order:2}.sidebar-tools-main:not(.tools-wide){vertical-align:middle}.sidebar-navigation .quarto-navigation-tool.dropdown-toggle::after{display:none}.sidebar.sidebar-navigation>*{padding-top:1em}.sidebar-item{margin-bottom:.2em;line-height:1rem;margin-top:.4rem}.sidebar-section{padding-left:.5em;padding-bottom:.2em}.sidebar-item .sidebar-item-container{display:flex;justify-content:space-between;cursor:pointer}.sidebar-item-toggle:hover{cursor:pointer}.sidebar-item .sidebar-item-toggle .bi{font-size:.7rem;text-align:center}.sidebar-item .sidebar-item-toggle .bi-chevron-right::before{transition:transform 200ms ease}.sidebar-item .sidebar-item-toggle[aria-expanded=false] .bi-chevron-right::before{transform:none}.sidebar-item .sidebar-item-toggle[aria-expanded=true] .bi-chevron-right::before{transform:rotate(90deg)}.sidebar-item-text{width:100%}.sidebar-navigation .sidebar-divider{margin-left:0;margin-right:0;margin-top:.5rem;margin-bottom:.5rem}@media(max-width: 991.98px){.quarto-secondary-nav{display:block}.quarto-secondary-nav button.quarto-search-button{padding-right:0em;padding-left:2em}.quarto-secondary-nav button.quarto-btn-toggle{margin-left:-0.75rem;margin-right:.15rem}.quarto-secondary-nav nav.quarto-title-breadcrumbs{display:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs{display:flex;align-items:center;padding-right:1em;margin-left:-0.25em}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{text-decoration:none}.quarto-secondary-nav nav.quarto-page-breadcrumbs ol.breadcrumb{margin-bottom:0}}@media(min-width: 992px){.quarto-secondary-nav{display:none}}.quarto-title-breadcrumbs .breadcrumb{margin-bottom:.5em;font-size:.9rem}.quarto-title-breadcrumbs .breadcrumb li:last-of-type a{color:#6c757d}.quarto-secondary-nav .quarto-btn-toggle{color:#545555}.quarto-secondary-nav[aria-expanded=false] .quarto-btn-toggle .bi-chevron-right::before{transform:none}.quarto-secondary-nav[aria-expanded=true] .quarto-btn-toggle .bi-chevron-right::before{transform:rotate(90deg)}.quarto-secondary-nav .quarto-btn-toggle .bi-chevron-right::before{transition:transform 200ms ease}.quarto-secondary-nav{cursor:pointer}.no-decor{text-decoration:none}.quarto-secondary-nav-title{margin-top:.3em;color:#545555;padding-top:4px}.quarto-secondary-nav nav.quarto-page-breadcrumbs{color:#545555}.quarto-secondary-nav nav.quarto-page-breadcrumbs a{color:#545555}.quarto-secondary-nav nav.quarto-page-breadcrumbs a:hover{color:rgba(0,0,255,.8)}.quarto-secondary-nav nav.quarto-page-breadcrumbs .breadcrumb-item::before{color:#878888}.breadcrumb-item{line-height:1.2rem}div.sidebar-item-container{color:#545555}div.sidebar-item-container:hover,div.sidebar-item-container:focus{color:rgba(0,0,255,.8)}div.sidebar-item-container.disabled{color:rgba(84,85,85,.75)}div.sidebar-item-container .active,div.sidebar-item-container .show>.nav-link,div.sidebar-item-container .sidebar-link>code{color:blue}div.sidebar.sidebar-navigation.rollup.quarto-sidebar-toggle-contents,nav.sidebar.sidebar-navigation:not(.rollup){background-color:#f8f9fa}.sidebar.sidebar-navigation:not(.rollup){border-right:1px solid #dee2e6 !important}@media(max-width: 991.98px){.sidebar-navigation .sidebar-item a,.nav-page .nav-page-text,.sidebar-navigation{font-size:1rem}.sidebar-navigation ul.sidebar-section.depth1 .sidebar-section-item{font-size:1.1rem}.sidebar-logo{display:none}.sidebar.sidebar-navigation{position:static;border-bottom:1px solid #dee2e6}.sidebar.sidebar-navigation.collapsing{position:fixed;z-index:1000}.sidebar.sidebar-navigation.show{position:fixed;z-index:1000}.sidebar.sidebar-navigation{min-height:100%}nav.quarto-secondary-nav{background-color:#f8f9fa;border-bottom:1px solid #dee2e6}.quarto-banner nav.quarto-secondary-nav{background-color:#517699;color:#fdfefe;border-top:1px solid #dee2e6}.sidebar .sidebar-footer{visibility:visible;padding-top:1rem;position:inherit}.sidebar-tools-collapse{display:block}}#quarto-sidebar{transition:width .15s ease-in}#quarto-sidebar>*{padding-right:1em}@media(max-width: 991.98px){#quarto-sidebar .sidebar-menu-container{white-space:nowrap;min-width:225px}#quarto-sidebar.show{transition:width .15s ease-out}}@media(min-width: 992px){#quarto-sidebar{display:flex;flex-direction:column}.nav-page .nav-page-text,.sidebar-navigation .sidebar-section .sidebar-item{font-size:.875rem}.sidebar-navigation .sidebar-item{font-size:.925rem}.sidebar.sidebar-navigation{display:block;position:sticky}.sidebar-search{width:100%}.sidebar .sidebar-footer{visibility:visible}}@media(max-width: 991.98px){#quarto-sidebar-glass{position:fixed;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);transition:background-color .15s ease-in;z-index:-1}#quarto-sidebar-glass.collapsing{z-index:1000}#quarto-sidebar-glass.show{transition:background-color .15s ease-out;background-color:rgba(102,102,102,.4);z-index:1000}}.sidebar .sidebar-footer{padding:.5rem 1rem;align-self:flex-end;color:#6c757d;width:100%}.quarto-page-breadcrumbs .breadcrumb-item+.breadcrumb-item,.quarto-page-breadcrumbs .breadcrumb-item{padding-right:.33em;padding-left:0}.quarto-page-breadcrumbs .breadcrumb-item::before{padding-right:.33em}.quarto-sidebar-footer{font-size:.875em}.sidebar-section .bi-chevron-right{vertical-align:middle}.sidebar-section .bi-chevron-right::before{font-size:.9em}.notransition{-webkit-transition:none !important;-moz-transition:none !important;-o-transition:none !important;transition:none !important}.btn:focus:not(:focus-visible){box-shadow:none}.page-navigation{display:flex;justify-content:space-between}.nav-page{padding-bottom:.75em}.nav-page .bi{font-size:1.8rem;vertical-align:middle}.nav-page .nav-page-text{padding-left:.25em;padding-right:.25em}.nav-page a{color:#6c757d;text-decoration:none;display:flex;align-items:center}.nav-page a:hover{color:#00c}.nav-footer .toc-actions{padding-bottom:.5em;padding-top:.5em}.nav-footer .toc-actions a,.nav-footer .toc-actions a:hover{text-decoration:none}.nav-footer .toc-actions ul{display:flex;list-style:none}.nav-footer .toc-actions ul :first-child{margin-left:auto}.nav-footer .toc-actions ul :last-child{margin-right:auto}.nav-footer .toc-actions ul li{padding-right:1.5em}.nav-footer .toc-actions ul li i.bi{padding-right:.4em}.nav-footer .toc-actions ul li:last-of-type{padding-right:0}.nav-footer{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:baseline;text-align:center;padding-top:.5rem;padding-bottom:.5rem;background-color:#fff}body.nav-fixed{padding-top:64px}.nav-footer-contents{color:#6c757d;margin-top:.25rem}.nav-footer{min-height:3.5em;color:#757575}.nav-footer a{color:#757575}.nav-footer .nav-footer-left{font-size:.825em}.nav-footer .nav-footer-center{font-size:.825em}.nav-footer .nav-footer-right{font-size:.825em}.nav-footer-left .footer-items,.nav-footer-center .footer-items,.nav-footer-right .footer-items{display:inline-flex;padding-top:.3em;padding-bottom:.3em;margin-bottom:0em}.nav-footer-left .footer-items .nav-link,.nav-footer-center .footer-items .nav-link,.nav-footer-right .footer-items .nav-link{padding-left:.6em;padding-right:.6em}.nav-footer-left{flex:1 1 0px;text-align:left}.nav-footer-right{flex:1 1 0px;text-align:right}.nav-footer-center{flex:1 1 0px;min-height:3em;text-align:center}.nav-footer-center .footer-items{justify-content:center}@media(max-width: 767.98px){.nav-footer-center{margin-top:3em}}.navbar .quarto-reader-toggle.reader .quarto-reader-toggle-btn{background-color:#fdfefe;border-radius:3px}@media(max-width: 991.98px){.quarto-reader-toggle{display:none}}.quarto-reader-toggle.reader.quarto-navigation-tool .quarto-reader-toggle-btn{background-color:#545555;border-radius:3px}.quarto-reader-toggle .quarto-reader-toggle-btn{display:inline-flex;padding-left:.2em;padding-right:.2em;margin-left:-0.2em;margin-right:-0.2em;text-align:center}.navbar .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle:not(.reader) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-reader-toggle.reader .bi::before{background-image:url('data:image/svg+xml,')}#quarto-back-to-top{display:none;position:fixed;bottom:50px;background-color:#fff;border-radius:.25rem;box-shadow:0 .2rem .5rem #6c757d,0 0 .05rem #6c757d;color:#6c757d;text-decoration:none;font-size:.9em;text-align:center;left:50%;padding:.4rem .8rem;transform:translate(-50%, 0)}.aa-DetachedSearchButtonQuery{display:none}.aa-DetachedOverlay ul.aa-List,#quarto-search-results ul.aa-List{list-style:none;padding-left:0}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{background-color:#fff;position:absolute;z-index:2000}#quarto-search-results .aa-Panel{max-width:400px}#quarto-search input{font-size:.925rem}@media(min-width: 992px){.navbar #quarto-search{margin-left:.25rem;order:999}}.navbar.navbar-expand-sm #quarto-search,.navbar.navbar-expand-md #quarto-search{order:999}@media(min-width: 992px){.navbar .quarto-navbar-tools{margin-left:auto;order:900}}@media(max-width: 991.98px){#quarto-sidebar .sidebar-search{display:none}}#quarto-sidebar .sidebar-search .aa-Autocomplete{width:100%}.navbar .aa-Autocomplete .aa-Form{width:180px}.navbar #quarto-search.type-overlay .aa-Autocomplete{width:40px}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form{background-color:inherit;border:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form:focus-within{box-shadow:none;outline:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper{display:none}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-InputWrapper:focus-within{display:inherit}.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-Label svg,.navbar #quarto-search.type-overlay .aa-Autocomplete .aa-Form .aa-LoadingIndicator svg{width:26px;height:26px;color:#fdfefe;opacity:1}.navbar #quarto-search.type-overlay .aa-Autocomplete svg.aa-SubmitIcon{width:26px;height:26px;color:#fdfefe;opacity:1}.aa-Autocomplete .aa-Form,.aa-DetachedFormContainer .aa-Form{align-items:center;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;color:#212529;display:flex;line-height:1em;margin:0;position:relative;width:100%}.aa-Autocomplete .aa-Form:focus-within,.aa-DetachedFormContainer .aa-Form:focus-within{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix{align-items:center;display:flex;flex-shrink:0;order:1}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{cursor:initial;flex-shrink:0;padding:0;text-align:left}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-Label svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator svg{color:#212529;opacity:.5}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-SubmitButton{appearance:none;background:none;border:0;margin:0}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator{align-items:center;display:flex;justify-content:center}.aa-Autocomplete .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperPrefix .aa-LoadingIndicator[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapper,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper{order:3;position:relative;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input{appearance:none;background:none;border:0;color:#212529;font:inherit;height:calc(1.5em + .1rem + 2px);padding:0;width:100%}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::placeholder,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::placeholder{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input:focus{border-color:none;box-shadow:none;outline:none}.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-Autocomplete .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-decoration,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-cancel-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-button,.aa-DetachedFormContainer .aa-Form .aa-InputWrapper .aa-Input::-webkit-search-results-decoration{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix{align-items:center;display:flex;order:4}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton{align-items:center;background:none;border:0;color:#212529;opacity:.8;cursor:pointer;display:flex;margin:0;width:calc(1.5em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton:focus{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton[hidden]{display:none}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-ClearButton svg{width:calc(1.5em + 0.75rem + calc(1px * 2))}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton{border:none;align-items:center;background:none;color:#212529;opacity:.4;font-size:.7rem;cursor:pointer;display:none;margin:0;width:calc(1em + .1rem + 2px)}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:hover,.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton:focus{color:#212529;opacity:.8}.aa-Autocomplete .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden],.aa-DetachedFormContainer .aa-Form .aa-InputWrapperSuffix .aa-CopyButton[hidden]{display:none}.aa-PanelLayout:empty{display:none}.quarto-search-no-results.no-query{display:none}.aa-Source:has(.no-query){display:none}#quarto-search-results .aa-Panel{border:solid #dee2e6 1px}#quarto-search-results .aa-SourceNoResults{width:398px}.aa-DetachedOverlay .aa-Panel,#quarto-search-results .aa-Panel{max-height:65vh;overflow-y:auto;font-size:.925rem}.aa-DetachedOverlay .aa-SourceNoResults,#quarto-search-results .aa-SourceNoResults{height:60px;display:flex;justify-content:center;align-items:center}.aa-DetachedOverlay .search-error,#quarto-search-results .search-error{padding-top:10px;padding-left:20px;padding-right:20px;cursor:default}.aa-DetachedOverlay .search-error .search-error-title,#quarto-search-results .search-error .search-error-title{font-size:1.1rem;margin-bottom:.5rem}.aa-DetachedOverlay .search-error .search-error-title .search-error-icon,#quarto-search-results .search-error .search-error-title .search-error-icon{margin-right:8px}.aa-DetachedOverlay .search-error .search-error-text,#quarto-search-results .search-error .search-error-text{font-weight:300}.aa-DetachedOverlay .search-result-text,#quarto-search-results .search-result-text{font-weight:300;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;line-height:1.2rem;max-height:2.4rem}.aa-DetachedOverlay .aa-SourceHeader .search-result-header,#quarto-search-results .aa-SourceHeader .search-result-header{font-size:.875rem;background-color:#f2f2f2;padding-left:14px;padding-bottom:4px;padding-top:4px}.aa-DetachedOverlay .aa-SourceHeader .search-result-header-no-results,#quarto-search-results .aa-SourceHeader .search-result-header-no-results{display:none}.aa-DetachedOverlay .aa-SourceFooter .algolia-search-logo,#quarto-search-results .aa-SourceFooter .algolia-search-logo{width:110px;opacity:.85;margin:8px;float:right}.aa-DetachedOverlay .search-result-section,#quarto-search-results .search-result-section{font-size:.925em}.aa-DetachedOverlay a.search-result-link,#quarto-search-results a.search-result-link{color:inherit;text-decoration:none}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item,#quarto-search-results li.aa-Item[aria-selected=true] .search-item{background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-result-text-container{color:#fff;background-color:#0d6efd}.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=true] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=true] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=true] .search-item .search-match.mark{color:#fff;background-color:#3586fd}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item,#quarto-search-results li.aa-Item[aria-selected=false] .search-item{background-color:#fff}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item.search-result-more,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-section,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-title-container,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-result-text-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item.search-result-more,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-section,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-title-container,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-result-text-container{color:#212529}.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item mark.search-match,.aa-DetachedOverlay li.aa-Item[aria-selected=false] .search-item .search-match.mark,#quarto-search-results li.aa-Item[aria-selected=false] .search-item mark.search-match,#quarto-search-results li.aa-Item[aria-selected=false] .search-item .search-match.mark{color:inherit;background-color:#e1edff}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-title-container{background-color:#fff;color:#212529}.aa-DetachedOverlay .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container,#quarto-search-results .aa-Item .search-result-doc:not(.document-selectable) .search-result-text-container{padding-top:0px}.aa-DetachedOverlay li.aa-Item .search-result-doc.document-selectable .search-result-text-container,#quarto-search-results li.aa-Item .search-result-doc.document-selectable .search-result-text-container{margin-top:-4px}.aa-DetachedOverlay .aa-Item,#quarto-search-results .aa-Item{cursor:pointer}.aa-DetachedOverlay .aa-Item .search-item,#quarto-search-results .aa-Item .search-item{border-left:none;border-right:none;border-top:none;background-color:#fff;border-color:#dee2e6;color:#212529}.aa-DetachedOverlay .aa-Item .search-item p,#quarto-search-results .aa-Item .search-item p{margin-top:0;margin-bottom:0}.aa-DetachedOverlay .aa-Item .search-item i.bi,#quarto-search-results .aa-Item .search-item i.bi{padding-left:8px;padding-right:8px;font-size:1.3em}.aa-DetachedOverlay .aa-Item .search-item .search-result-title,#quarto-search-results .aa-Item .search-item .search-result-title{margin-top:.3em;margin-bottom:0em}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs,#quarto-search-results .aa-Item .search-item .search-result-crumbs{white-space:nowrap;text-overflow:ellipsis;font-size:.8em;font-weight:300;margin-right:1em}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs:not(.search-result-crumbs-wrap),#quarto-search-results .aa-Item .search-item .search-result-crumbs:not(.search-result-crumbs-wrap){max-width:30%;margin-left:auto;margin-top:.5em;margin-bottom:.1rem}.aa-DetachedOverlay .aa-Item .search-item .search-result-crumbs.search-result-crumbs-wrap,#quarto-search-results .aa-Item .search-item .search-result-crumbs.search-result-crumbs-wrap{flex-basis:100%;margin-top:0em;margin-bottom:.2em;margin-left:37px}.aa-DetachedOverlay .aa-Item .search-result-title-container,#quarto-search-results .aa-Item .search-result-title-container{font-size:1em;display:flex;flex-wrap:wrap;padding:6px 4px 6px 4px}.aa-DetachedOverlay .aa-Item .search-result-text-container,#quarto-search-results .aa-Item .search-result-text-container{padding-bottom:8px;padding-right:8px;margin-left:42px}.aa-DetachedOverlay .aa-Item .search-result-doc-section,.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-doc-section,#quarto-search-results .aa-Item .search-result-more{padding-top:8px;padding-bottom:8px;padding-left:44px}.aa-DetachedOverlay .aa-Item .search-result-more,#quarto-search-results .aa-Item .search-result-more{font-size:.8em;font-weight:400}.aa-DetachedOverlay .aa-Item .search-result-doc,#quarto-search-results .aa-Item .search-result-doc{border-top:1px solid #dee2e6}.aa-DetachedSearchButton{background:none;border:none}.aa-DetachedSearchButton .aa-DetachedSearchButtonPlaceholder{display:none}.navbar .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#fdfefe}.sidebar-tools-collapse #quarto-search,.sidebar-tools-main #quarto-search{display:inline}.sidebar-tools-collapse #quarto-search .aa-Autocomplete,.sidebar-tools-main #quarto-search .aa-Autocomplete{display:inline}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton{padding-left:4px;padding-right:4px}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon{color:#545555}.sidebar-tools-collapse #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon,.sidebar-tools-main #quarto-search .aa-DetachedSearchButton .aa-DetachedSearchButtonIcon .aa-SubmitIcon{margin-top:-3px}.aa-DetachedContainer{background:rgba(255,255,255,.65);width:90%;bottom:0;box-shadow:rgba(222,226,230,.6) 0 0 0 1px;outline:currentColor none medium;display:flex;flex-direction:column;left:0;margin:0;overflow:hidden;padding:0;position:fixed;right:0;top:0;z-index:1101}.aa-DetachedContainer::after{height:32px}.aa-DetachedContainer .aa-SourceHeader{margin:var(--aa-spacing-half) 0 var(--aa-spacing-half) 2px}.aa-DetachedContainer .aa-Panel{background-color:#fff;border-radius:0;box-shadow:none;flex-grow:1;margin:0;padding:0;position:relative}.aa-DetachedContainer .aa-PanelLayout{bottom:0;box-shadow:none;left:0;margin:0;max-height:none;overflow-y:auto;position:absolute;right:0;top:0;width:100%}.aa-DetachedFormContainer{background-color:#fff;border-bottom:1px solid #dee2e6;display:flex;flex-direction:row;justify-content:space-between;margin:0;padding:.5em}.aa-DetachedCancelButton{background:none;font-size:.8em;border:0;border-radius:3px;color:#212529;cursor:pointer;margin:0 0 0 .5em;padding:0 .5em}.aa-DetachedCancelButton:hover,.aa-DetachedCancelButton:focus{box-shadow:rgba(13,110,253,.6) 0 0 0 1px;outline:currentColor none medium}.aa-DetachedContainer--modal{bottom:inherit;height:auto;margin:0 auto;position:absolute;top:100px;border-radius:6px;max-width:850px}@media(max-width: 575.98px){.aa-DetachedContainer--modal{width:100%;top:0px;border-radius:0px;border:none}}.aa-DetachedContainer--modal .aa-PanelLayout{max-height:var(--aa-detached-modal-max-height);padding-bottom:var(--aa-spacing-half);position:static}.aa-Detached{height:100vh;overflow:hidden}.aa-DetachedOverlay{background-color:rgba(33,37,41,.4);position:fixed;left:0;right:0;top:0;margin:0;padding:0;height:100vh;z-index:1100}.quarto-dashboard.nav-fixed.dashboard-sidebar #quarto-content.quarto-dashboard-content{padding:0em}.quarto-dashboard #quarto-content.quarto-dashboard-content{padding:1em}.quarto-dashboard #quarto-content.quarto-dashboard-content>*{padding-top:0}@media(min-width: 576px){.quarto-dashboard{height:100%}}.quarto-dashboard .card.valuebox.bslib-card.bg-primary{background-color:#0d6efd !important}.quarto-dashboard .card.valuebox.bslib-card.bg-secondary{background-color:#6c757d !important}.quarto-dashboard .card.valuebox.bslib-card.bg-success{background-color:#198754 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-info{background-color:#0dcaf0 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-warning{background-color:#ffc107 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-danger{background-color:#dc3545 !important}.quarto-dashboard .card.valuebox.bslib-card.bg-light{background-color:#f8f9fa !important}.quarto-dashboard .card.valuebox.bslib-card.bg-dark{background-color:#212529 !important}.quarto-dashboard.dashboard-fill{display:flex;flex-direction:column}.quarto-dashboard #quarto-appendix{display:none}.quarto-dashboard #quarto-header #quarto-dashboard-header{border-top:solid 1px #6c8fb1;border-bottom:solid 1px #6c8fb1}.quarto-dashboard #quarto-header #quarto-dashboard-header>nav{padding-left:1em;padding-right:1em}.quarto-dashboard #quarto-header #quarto-dashboard-header>nav .navbar-brand-container{padding-left:0}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-toggler{margin-right:0}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-toggler-icon{height:1em;width:1em;background-image:url('data:image/svg+xml,')}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-brand-container{padding-right:1em}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-title{font-size:1.1em}.quarto-dashboard #quarto-header #quarto-dashboard-header .navbar-nav{font-size:.9em}.quarto-dashboard #quarto-dashboard-header .navbar{padding:0}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-container{padding-left:1em}.quarto-dashboard #quarto-dashboard-header .navbar.slim .navbar-brand-container .nav-link,.quarto-dashboard #quarto-dashboard-header .navbar.slim .navbar-nav .nav-link{padding:.7em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-color-scheme-toggle{order:9}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-toggler{margin-left:.5em;order:10}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-nav .nav-link{padding:.5em;height:100%;display:flex;align-items:center}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-nav .active{background-color:#658aae}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-brand-container{padding:.5em .5em .5em 0;display:flex;flex-direction:row;margin-right:2em;align-items:center}@media(max-width: 767.98px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-brand-container{margin-right:auto}}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{align-self:stretch}@media(min-width: 768px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{order:8}}@media(max-width: 767.98px){.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse{order:1000;padding-bottom:.5em}}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-collapse .navbar-nav{align-self:stretch}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title{font-size:1.25em;line-height:1.1em;display:flex;flex-direction:row;flex-wrap:wrap;align-items:baseline}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title .navbar-title-text{margin-right:.4em}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-title a{text-decoration:none;color:inherit}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-subtitle,.quarto-dashboard #quarto-dashboard-header .navbar .navbar-author{font-size:.9rem;margin-right:.5em}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-author{margin-left:auto}.quarto-dashboard #quarto-dashboard-header .navbar .navbar-logo{max-height:48px;min-height:30px;object-fit:cover;margin-right:1em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-links{order:9;padding-right:1em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-link-text{margin-left:.25em}.quarto-dashboard #quarto-dashboard-header .navbar .quarto-dashboard-link{padding-right:0em;padding-left:.7em;text-decoration:none;color:#fdfefe}.quarto-dashboard .page-layout-custom .tab-content{padding:0;border:none}.quarto-dashboard-img-contain{height:100%;width:100%;object-fit:contain}@media(max-width: 575.98px){.quarto-dashboard .bslib-grid{grid-template-rows:minmax(1em, max-content) !important}.quarto-dashboard .sidebar-content{height:inherit}.quarto-dashboard .page-layout-custom{min-height:100vh}}.quarto-dashboard.dashboard-toolbar>.page-layout-custom,.quarto-dashboard.dashboard-sidebar>.page-layout-custom{padding:0}.quarto-dashboard .quarto-dashboard-content.quarto-dashboard-pages{padding:0}.quarto-dashboard .callout{margin-bottom:0;margin-top:0}.quarto-dashboard .html-fill-container figure{overflow:hidden}.quarto-dashboard bslib-tooltip .rounded-pill{border:solid #6c757d 1px}.quarto-dashboard bslib-tooltip .rounded-pill .svg{fill:#212529}.quarto-dashboard .tabset .dashboard-card-no-title .nav-tabs{margin-left:0;margin-right:auto}.quarto-dashboard .tabset .tab-content{border:none}.quarto-dashboard .tabset .card-header .nav-link[role=tab]{margin-top:-6px;padding-top:6px;padding-bottom:6px}.quarto-dashboard .card.valuebox,.quarto-dashboard .card.bslib-value-box{min-height:3rem}.quarto-dashboard .card.valuebox .card-body,.quarto-dashboard .card.bslib-value-box .card-body{padding:0}.quarto-dashboard .bslib-value-box .value-box-value{font-size:clamp(.1em,15cqw,5em)}.quarto-dashboard .bslib-value-box .value-box-showcase .bi{font-size:clamp(.1em,max(18cqw,5.2cqh),5em);text-align:center;height:1em}.quarto-dashboard .bslib-value-box .value-box-showcase .bi::before{vertical-align:1em}.quarto-dashboard .bslib-value-box .value-box-area{margin-top:auto;margin-bottom:auto}.quarto-dashboard .card figure.quarto-float{display:flex;flex-direction:column;align-items:center}.quarto-dashboard .dashboard-scrolling{padding:1em}.quarto-dashboard .full-height{height:100%}.quarto-dashboard .showcase-bottom .value-box-grid{display:grid;grid-template-columns:1fr;grid-template-rows:1fr auto;grid-template-areas:"top" "bottom"}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-showcase{grid-area:bottom;padding:0;margin:0}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-showcase i.bi{font-size:4rem}.quarto-dashboard .showcase-bottom .value-box-grid .value-box-area{grid-area:top}.quarto-dashboard .tab-content{margin-bottom:0}.quarto-dashboard .bslib-card .bslib-navs-card-title{justify-content:stretch;align-items:end}.quarto-dashboard .card-header{display:flex;flex-wrap:wrap;justify-content:space-between}.quarto-dashboard .card-header .card-title{display:flex;flex-direction:column;justify-content:center;margin-bottom:0}.quarto-dashboard .tabset .card-toolbar{margin-bottom:1em}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout{border:none;gap:var(--bslib-spacer, 1rem)}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.main{padding:0}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.sidebar{border-radius:.25rem;border:1px solid rgba(0,0,0,.175)}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.collapse-toggle{display:none}@media(max-width: 767.98px){.quarto-dashboard .bslib-grid>.bslib-sidebar-layout{grid-template-columns:1fr;grid-template-rows:max-content 1fr}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout>.main{grid-column:1;grid-row:2}.quarto-dashboard .bslib-grid>.bslib-sidebar-layout .sidebar{grid-column:1;grid-row:1}}.quarto-dashboard .sidebar-right .sidebar{padding-left:2.5em}.quarto-dashboard .sidebar-right .collapse-toggle{left:2px}.quarto-dashboard .quarto-dashboard .sidebar-right button.collapse-toggle:not(.transitioning){left:unset}.quarto-dashboard aside.sidebar{padding-left:1em;padding-right:1em;background-color:rgba(52,58,64,.25);color:#212529}.quarto-dashboard .bslib-sidebar-layout>div.main{padding:.7em}.quarto-dashboard .bslib-sidebar-layout button.collapse-toggle{margin-top:.3em}.quarto-dashboard .bslib-sidebar-layout .collapse-toggle{top:0}.quarto-dashboard .bslib-sidebar-layout.sidebar-collapsed:not(.transitioning):not(.sidebar-right) .collapse-toggle{left:2px}.quarto-dashboard .sidebar>section>.h3:first-of-type{margin-top:0em}.quarto-dashboard .sidebar .h3,.quarto-dashboard .sidebar .h4,.quarto-dashboard .sidebar .h5,.quarto-dashboard .sidebar .h6{margin-top:.5em}.quarto-dashboard .sidebar form{flex-direction:column;align-items:start;margin-bottom:1em}.quarto-dashboard .sidebar form div[class*=oi-][class$=-input]{flex-direction:column}.quarto-dashboard .sidebar form[class*=oi-][class$=-toggle]{flex-direction:row-reverse;align-items:center;justify-content:start}.quarto-dashboard .sidebar form input[type=range]{margin-top:.5em;margin-right:.8em;margin-left:1em}.quarto-dashboard .sidebar label{width:fit-content}.quarto-dashboard .sidebar .card-body{margin-bottom:2em}.quarto-dashboard .sidebar .shiny-input-container{margin-bottom:1em}.quarto-dashboard .sidebar .shiny-options-group{margin-top:0}.quarto-dashboard .sidebar .control-label{margin-bottom:.3em}.quarto-dashboard .card .card-body .quarto-layout-row{align-items:stretch}.quarto-dashboard .toolbar{font-size:.9em;display:flex;flex-direction:row;border-top:solid 1px #c7c9cd;padding:1em;flex-wrap:wrap;background-color:rgba(52,58,64,.25)}.quarto-dashboard .toolbar .cell-output-display{display:flex}.quarto-dashboard .toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .toolbar>*:last-child{margin-right:0}.quarto-dashboard .toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .toolbar .shiny-input-container{padding-bottom:0;margin-bottom:0}.quarto-dashboard .toolbar .shiny-input-container>*{flex-shrink:0;flex-grow:0}.quarto-dashboard .toolbar .form-group.shiny-input-container:not([role=group])>label{margin-bottom:0}.quarto-dashboard .toolbar .shiny-input-container.no-baseline{align-items:start;padding-top:6px}.quarto-dashboard .toolbar .shiny-input-container{display:flex;align-items:baseline}.quarto-dashboard .toolbar .shiny-input-container label{padding-right:.4em}.quarto-dashboard .toolbar .shiny-input-container .bslib-input-switch{margin-top:6px}.quarto-dashboard .toolbar input[type=text]{line-height:1;width:inherit}.quarto-dashboard .toolbar .input-daterange{width:inherit}.quarto-dashboard .toolbar .input-daterange input[type=text]{height:2.4em;width:10em}.quarto-dashboard .toolbar .input-daterange .input-group-addon{height:auto;padding:0;margin-left:-5px !important;margin-right:-5px}.quarto-dashboard .toolbar .input-daterange .input-group-addon .input-group-text{padding-top:0;padding-bottom:0;height:100%}.quarto-dashboard .toolbar span.irs.irs--shiny{width:10em}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-line{top:9px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-min,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-max,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-from,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-to,.quarto-dashboard .toolbar span.irs.irs--shiny .irs-single{top:20px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-bar{top:8px}.quarto-dashboard .toolbar span.irs.irs--shiny .irs-handle{top:0px}.quarto-dashboard .toolbar .shiny-input-checkboxgroup>label{margin-top:6px}.quarto-dashboard .toolbar .shiny-input-checkboxgroup>.shiny-options-group{margin-top:0;align-items:baseline}.quarto-dashboard .toolbar .shiny-input-radiogroup>label{margin-top:6px}.quarto-dashboard .toolbar .shiny-input-radiogroup>.shiny-options-group{align-items:baseline;margin-top:0}.quarto-dashboard .toolbar .shiny-input-radiogroup>.shiny-options-group>.radio{margin-right:.3em}.quarto-dashboard .toolbar .form-select{padding-top:.2em;padding-bottom:.2em}.quarto-dashboard .toolbar .shiny-input-select{min-width:6em}.quarto-dashboard .toolbar div.checkbox{margin-bottom:0px}.quarto-dashboard .toolbar>.checkbox:first-child{margin-top:6px}.quarto-dashboard .toolbar form{width:fit-content}.quarto-dashboard .toolbar form label{padding-top:.2em;padding-bottom:.2em;width:fit-content}.quarto-dashboard .toolbar form input[type=date]{width:fit-content}.quarto-dashboard .toolbar form input[type=color]{width:3em}.quarto-dashboard .toolbar form button{padding:.4em}.quarto-dashboard .toolbar form select{width:fit-content}.quarto-dashboard .toolbar>*{font-size:.9em;flex-grow:0}.quarto-dashboard .toolbar .shiny-input-container label{margin-bottom:1px}.quarto-dashboard .toolbar-bottom{margin-top:1em;margin-bottom:0 !important;order:2}.quarto-dashboard .quarto-dashboard-content>.dashboard-toolbar-container>.toolbar-content>.tab-content>.tab-pane>*:not(.bslib-sidebar-layout){padding:1em}.quarto-dashboard .quarto-dashboard-content>.dashboard-toolbar-container>.toolbar-content>*:not(.tab-content){padding:1em}.quarto-dashboard .quarto-dashboard-content>.tab-content>.dashboard-page>.dashboard-toolbar-container>.toolbar-content,.quarto-dashboard .quarto-dashboard-content>.tab-content>.dashboard-page:not(.dashboard-sidebar-container)>*:not(.dashboard-toolbar-container){padding:1em}.quarto-dashboard .toolbar-content{padding:0}.quarto-dashboard .quarto-dashboard-content.quarto-dashboard-pages .tab-pane>.dashboard-toolbar-container .toolbar{border-radius:0;margin-bottom:0}.quarto-dashboard .dashboard-toolbar-container.toolbar-toplevel .toolbar{border-bottom:1px solid rgba(0,0,0,.175)}.quarto-dashboard .dashboard-toolbar-container.toolbar-toplevel .toolbar-bottom{margin-top:0}.quarto-dashboard .dashboard-toolbar-container:not(.toolbar-toplevel) .toolbar{margin-bottom:1em;border-top:none;border-radius:.25rem;border:1px solid rgba(0,0,0,.175)}.quarto-dashboard .vega-embed.has-actions details{width:1.7em;height:2em;position:absolute !important;top:0;right:0}.quarto-dashboard .dashboard-toolbar-container{padding:0}.quarto-dashboard .card .card-header p:last-child,.quarto-dashboard .card .card-footer p:last-child{margin-bottom:0}.quarto-dashboard .card .card-body>.h4:first-child{margin-top:0}.quarto-dashboard .card .card-body{z-index:1000}@media(max-width: 767.98px){.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_length,.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_info,.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_paginate{text-align:initial}.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_filter{text-align:right}.quarto-dashboard .card .card-body .itables div.dataTables_wrapper div.dataTables_paginate ul.pagination{justify-content:initial}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;padding-top:0}.quarto-dashboard .card .card-body .itables .dataTables_wrapper table{flex-shrink:0}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons{margin-bottom:.5em;margin-left:auto;width:fit-content;float:right}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons.btn-group{background:#fff;border:none}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn-secondary{background-color:#fff;background-image:none;border:solid #dee2e6 1px;padding:.2em .7em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dt-buttons .btn span{font-size:.8em;color:#212529}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{margin-left:.5em;margin-bottom:.5em;padding-top:0}@media(min-width: 768px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{font-size:.875em}}@media(max-width: 767.98px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_info{font-size:.8em}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_filter{margin-bottom:.5em;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_filter input[type=search]{padding:1px 5px 1px 5px;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_length{flex-basis:1 1 50%;margin-bottom:.5em;font-size:.875em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_length select{padding:.4em 3em .4em .5em;font-size:.875em;margin-left:.2em;margin-right:.2em}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate{flex-shrink:0}@media(min-width: 768px){.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate{margin-left:auto}}.quarto-dashboard .card .card-body .itables .dataTables_wrapper .dataTables_paginate ul.pagination .paginate_button .page-link{font-size:.8em}.quarto-dashboard .card .card-footer{font-size:.9em}.quarto-dashboard .card .card-toolbar{display:flex;flex-grow:1;flex-direction:row;width:100%;flex-wrap:wrap}.quarto-dashboard .card .card-toolbar>*{font-size:.8em;flex-grow:0}.quarto-dashboard .card .card-toolbar>.card-title{font-size:1em;flex-grow:1;align-self:flex-start;margin-top:.1em}.quarto-dashboard .card .card-toolbar .cell-output-display{display:flex}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .card .card-toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card .card-toolbar>*:last-child{margin-right:0}.quarto-dashboard .card .card-toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .card .card-toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .card .card-toolbar form{width:fit-content}.quarto-dashboard .card .card-toolbar form label{padding-top:.2em;padding-bottom:.2em;width:fit-content}.quarto-dashboard .card .card-toolbar form input[type=date]{width:fit-content}.quarto-dashboard .card .card-toolbar form input[type=color]{width:3em}.quarto-dashboard .card .card-toolbar form button{padding:.4em}.quarto-dashboard .card .card-toolbar form select{width:fit-content}.quarto-dashboard .card .card-toolbar .cell-output-display{display:flex}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:.5em;margin-bottom:.5em;width:inherit}.quarto-dashboard .card .card-toolbar .shiny-input-container>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card .card-toolbar>*:last-child{margin-right:0}.quarto-dashboard .card .card-toolbar>*>*{margin-right:1em;align-items:baseline}.quarto-dashboard .card .card-toolbar>*>*>a{text-decoration:none;margin-top:auto;margin-bottom:auto}.quarto-dashboard .card .card-toolbar .shiny-input-container{padding-bottom:0;margin-bottom:0}.quarto-dashboard .card .card-toolbar .shiny-input-container>*{flex-shrink:0;flex-grow:0}.quarto-dashboard .card .card-toolbar .form-group.shiny-input-container:not([role=group])>label{margin-bottom:0}.quarto-dashboard .card .card-toolbar .shiny-input-container.no-baseline{align-items:start;padding-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-container{display:flex;align-items:baseline}.quarto-dashboard .card .card-toolbar .shiny-input-container label{padding-right:.4em}.quarto-dashboard .card .card-toolbar .shiny-input-container .bslib-input-switch{margin-top:6px}.quarto-dashboard .card .card-toolbar input[type=text]{line-height:1;width:inherit}.quarto-dashboard .card .card-toolbar .input-daterange{width:inherit}.quarto-dashboard .card .card-toolbar .input-daterange input[type=text]{height:2.4em;width:10em}.quarto-dashboard .card .card-toolbar .input-daterange .input-group-addon{height:auto;padding:0;margin-left:-5px !important;margin-right:-5px}.quarto-dashboard .card .card-toolbar .input-daterange .input-group-addon .input-group-text{padding-top:0;padding-bottom:0;height:100%}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny{width:10em}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-line{top:9px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-min,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-max,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-from,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-to,.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-single{top:20px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-bar{top:8px}.quarto-dashboard .card .card-toolbar span.irs.irs--shiny .irs-handle{top:0px}.quarto-dashboard .card .card-toolbar .shiny-input-checkboxgroup>label{margin-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-checkboxgroup>.shiny-options-group{margin-top:0;align-items:baseline}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>label{margin-top:6px}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>.shiny-options-group{align-items:baseline;margin-top:0}.quarto-dashboard .card .card-toolbar .shiny-input-radiogroup>.shiny-options-group>.radio{margin-right:.3em}.quarto-dashboard .card .card-toolbar .form-select{padding-top:.2em;padding-bottom:.2em}.quarto-dashboard .card .card-toolbar .shiny-input-select{min-width:6em}.quarto-dashboard .card .card-toolbar div.checkbox{margin-bottom:0px}.quarto-dashboard .card .card-toolbar>.checkbox:first-child{margin-top:6px}.quarto-dashboard .card-body>table>thead{border-top:none}.quarto-dashboard .card-body>.table>:not(caption)>*>*{background-color:#fff}.quarto-listing{padding-bottom:1em}.listing-pagination{padding-top:.5em}ul.pagination{float:right;padding-left:8px;padding-top:.5em}ul.pagination li{padding-right:.75em}ul.pagination li.disabled a,ul.pagination li.active a{color:#fff;text-decoration:none}ul.pagination li:last-of-type{padding-right:0}.listing-actions-group{display:flex}.quarto-listing-filter{margin-bottom:1em;width:200px;margin-left:auto}.quarto-listing-sort{margin-bottom:1em;margin-right:auto;width:auto}.quarto-listing-sort .input-group-text{font-size:.8em}.input-group-text{border-right:none}.quarto-listing-sort select.form-select{font-size:.8em}.listing-no-matching{text-align:center;padding-top:2em;padding-bottom:3em;font-size:1em}#quarto-margin-sidebar .quarto-listing-category{padding-top:0;font-size:1rem}#quarto-margin-sidebar .quarto-listing-category-title{cursor:pointer;font-weight:600;font-size:1rem}.quarto-listing-category .category{cursor:pointer}.quarto-listing-category .category.active{font-weight:600}.quarto-listing-category.category-cloud{display:flex;flex-wrap:wrap;align-items:baseline}.quarto-listing-category.category-cloud .category{padding-right:5px}.quarto-listing-category.category-cloud .category-cloud-1{font-size:.75em}.quarto-listing-category.category-cloud .category-cloud-2{font-size:.95em}.quarto-listing-category.category-cloud .category-cloud-3{font-size:1.15em}.quarto-listing-category.category-cloud .category-cloud-4{font-size:1.35em}.quarto-listing-category.category-cloud .category-cloud-5{font-size:1.55em}.quarto-listing-category.category-cloud .category-cloud-6{font-size:1.75em}.quarto-listing-category.category-cloud .category-cloud-7{font-size:1.95em}.quarto-listing-category.category-cloud .category-cloud-8{font-size:2.15em}.quarto-listing-category.category-cloud .category-cloud-9{font-size:2.35em}.quarto-listing-category.category-cloud .category-cloud-10{font-size:2.55em}.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-1{grid-template-columns:repeat(1, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-1{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-2{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-3{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-3{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-4{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-4{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-5{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-5{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-6{grid-template-columns:repeat(6, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-6{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-6{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-7{grid-template-columns:repeat(7, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-7{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-7{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-8{grid-template-columns:repeat(8, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-8{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-8{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-9{grid-template-columns:repeat(9, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-9{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-9{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-10{grid-template-columns:repeat(10, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-10{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-10{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-11{grid-template-columns:repeat(11, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-11{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-11{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-cols-12{grid-template-columns:repeat(12, minmax(0, 1fr));gap:1.5em}@media(max-width: 767.98px){.quarto-listing-cols-12{grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.5em}}@media(max-width: 575.98px){.quarto-listing-cols-12{grid-template-columns:minmax(0, 1fr);gap:1.5em}}.quarto-listing-grid{gap:1.5em}.quarto-grid-item.borderless{border:none}.quarto-grid-item.borderless .listing-categories .listing-category:last-of-type,.quarto-grid-item.borderless .listing-categories .listing-category:first-of-type{padding-left:0}.quarto-grid-item.borderless .listing-categories .listing-category{border:0}.quarto-grid-link{text-decoration:none;color:inherit}.quarto-grid-link:hover{text-decoration:none;color:inherit}.quarto-grid-item h5.title,.quarto-grid-item .title.h5{margin-top:0;margin-bottom:0}.quarto-grid-item .card-footer{display:flex;justify-content:space-between;font-size:.8em}.quarto-grid-item .card-footer p{margin-bottom:0}.quarto-grid-item p.card-img-top{margin-bottom:0}.quarto-grid-item p.card-img-top>img{object-fit:cover}.quarto-grid-item .card-other-values{margin-top:.5em;font-size:.8em}.quarto-grid-item .card-other-values tr{margin-bottom:.5em}.quarto-grid-item .card-other-values tr>td:first-of-type{font-weight:600;padding-right:1em;padding-left:1em;vertical-align:top}.quarto-grid-item div.post-contents{display:flex;flex-direction:column;text-decoration:none;height:100%}.quarto-grid-item .listing-item-img-placeholder{background-color:rgba(52,58,64,.25);flex-shrink:0}.quarto-grid-item .card-attribution{padding-top:1em;display:flex;gap:1em;text-transform:uppercase;color:#6c757d;font-weight:500;flex-grow:10;align-items:flex-end}.quarto-grid-item .description{padding-bottom:1em}.quarto-grid-item .card-attribution .date{align-self:flex-end}.quarto-grid-item .card-attribution.justify{justify-content:space-between}.quarto-grid-item .card-attribution.start{justify-content:flex-start}.quarto-grid-item .card-attribution.end{justify-content:flex-end}.quarto-grid-item .card-title{margin-bottom:.1em}.quarto-grid-item .card-subtitle{padding-top:.25em}.quarto-grid-item .card-text{font-size:.9em}.quarto-grid-item .listing-reading-time{padding-bottom:.25em}.quarto-grid-item .card-text-small{font-size:.8em}.quarto-grid-item .card-subtitle.subtitle{font-size:.9em;font-weight:600;padding-bottom:.5em}.quarto-grid-item .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}.quarto-grid-item .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}.quarto-grid-item.card-right{text-align:right}.quarto-grid-item.card-right .listing-categories{justify-content:flex-end}.quarto-grid-item.card-left{text-align:left}.quarto-grid-item.card-center{text-align:center}.quarto-grid-item.card-center .listing-description{text-align:justify}.quarto-grid-item.card-center .listing-categories{justify-content:center}table.quarto-listing-table td.image{padding:0px}table.quarto-listing-table td.image img{width:100%;max-width:50px;object-fit:contain}table.quarto-listing-table a{text-decoration:none;word-break:keep-all}table.quarto-listing-table th a{color:inherit}table.quarto-listing-table th a.asc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table th a.desc:after{margin-bottom:-2px;margin-left:5px;display:inline-block;height:1rem;width:1rem;background-repeat:no-repeat;background-size:1rem 1rem;background-image:url('data:image/svg+xml,');content:""}table.quarto-listing-table.table-hover td{cursor:pointer}.quarto-post.image-left{flex-direction:row}.quarto-post.image-right{flex-direction:row-reverse}@media(max-width: 767.98px){.quarto-post.image-right,.quarto-post.image-left{gap:0em;flex-direction:column}.quarto-post .metadata{padding-bottom:1em;order:2}.quarto-post .body{order:1}.quarto-post .thumbnail{order:3}}.list.quarto-listing-default div:last-of-type{border-bottom:none}@media(min-width: 992px){.quarto-listing-container-default{margin-right:2em}}div.quarto-post{display:flex;gap:2em;margin-bottom:1.5em;border-bottom:1px solid #dee2e6}@media(max-width: 767.98px){div.quarto-post{padding-bottom:1em}}div.quarto-post .metadata{flex-basis:20%;flex-grow:0;margin-top:.2em;flex-shrink:10}div.quarto-post .thumbnail{flex-basis:30%;flex-grow:0;flex-shrink:0}div.quarto-post .thumbnail img{margin-top:.4em;width:100%;object-fit:cover}div.quarto-post .body{flex-basis:45%;flex-grow:1;flex-shrink:0}div.quarto-post .body h3.listing-title,div.quarto-post .body .listing-title.h3{margin-top:0px;margin-bottom:0px;border-bottom:none}div.quarto-post .body .listing-subtitle{font-size:.875em;margin-bottom:.5em;margin-top:.2em}div.quarto-post .body .description{font-size:.9em}div.quarto-post .body pre code{white-space:pre-wrap}div.quarto-post a{color:#212529;text-decoration:none}div.quarto-post .metadata{display:flex;flex-direction:column;font-size:.8em;font-family:Roboto;flex-basis:33%}div.quarto-post .listing-categories{display:flex;flex-wrap:wrap;padding-bottom:5px}div.quarto-post .listing-categories .listing-category{color:#6c757d;border:solid 1px #dee2e6;border-radius:.25rem;text-transform:uppercase;font-size:.65em;padding-left:.5em;padding-right:.5em;padding-top:.15em;padding-bottom:.15em;cursor:pointer;margin-right:4px;margin-bottom:4px}div.quarto-post .listing-description{margin-bottom:.5em}div.quarto-about-jolla{display:flex !important;flex-direction:column;align-items:center;margin-top:10%;padding-bottom:1em}div.quarto-about-jolla .about-image{object-fit:cover;margin-left:auto;margin-right:auto;margin-bottom:1.5em}div.quarto-about-jolla img.round{border-radius:50%}div.quarto-about-jolla img.rounded{border-radius:10px}div.quarto-about-jolla .quarto-title h1.title,div.quarto-about-jolla .quarto-title .title.h1{text-align:center}div.quarto-about-jolla .quarto-title .description{text-align:center}div.quarto-about-jolla h2,div.quarto-about-jolla .h2{border-bottom:none}div.quarto-about-jolla .about-sep{width:60%}div.quarto-about-jolla main{text-align:center}div.quarto-about-jolla .about-links{display:flex}@media(min-width: 992px){div.quarto-about-jolla .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-jolla .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-jolla .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-jolla .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-jolla .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-jolla .about-link:hover{color:blue}div.quarto-about-jolla .about-link i.bi{margin-right:.15em}div.quarto-about-solana{display:flex !important;flex-direction:column;padding-top:3em !important;padding-bottom:1em}div.quarto-about-solana .about-entity{display:flex !important;align-items:start;justify-content:space-between}@media(min-width: 992px){div.quarto-about-solana .about-entity{flex-direction:row}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity{flex-direction:column-reverse;align-items:center;text-align:center}}div.quarto-about-solana .about-entity .entity-contents{display:flex;flex-direction:column}@media(max-width: 767.98px){div.quarto-about-solana .about-entity .entity-contents{width:100%}}div.quarto-about-solana .about-entity .about-image{object-fit:cover}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-image{margin-bottom:1.5em}}div.quarto-about-solana .about-entity img.round{border-radius:50%}div.quarto-about-solana .about-entity img.rounded{border-radius:10px}div.quarto-about-solana .about-entity .about-links{display:flex;justify-content:left;padding-bottom:1.2em}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-solana .about-entity .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-solana .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-solana .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-solana .about-entity .about-link:hover{color:blue}div.quarto-about-solana .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-solana .about-contents{padding-right:1.5em;flex-basis:0;flex-grow:1}div.quarto-about-solana .about-contents main.content{margin-top:0}div.quarto-about-solana .about-contents h2,div.quarto-about-solana .about-contents .h2{border-bottom:none}div.quarto-about-trestles{display:flex !important;flex-direction:row;padding-top:3em !important;padding-bottom:1em}@media(max-width: 991.98px){div.quarto-about-trestles{flex-direction:column;padding-top:0em !important}}div.quarto-about-trestles .about-entity{display:flex !important;flex-direction:column;align-items:center;text-align:center;padding-right:1em}@media(min-width: 992px){div.quarto-about-trestles .about-entity{flex:0 0 42%}}div.quarto-about-trestles .about-entity .about-image{object-fit:cover;margin-bottom:1.5em}div.quarto-about-trestles .about-entity img.round{border-radius:50%}div.quarto-about-trestles .about-entity img.rounded{border-radius:10px}div.quarto-about-trestles .about-entity .about-links{display:flex;justify-content:center}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-trestles .about-entity .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-trestles .about-entity .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-trestles .about-entity .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-trestles .about-entity .about-link:hover{color:blue}div.quarto-about-trestles .about-entity .about-link i.bi{margin-right:.15em}div.quarto-about-trestles .about-contents{flex-basis:0;flex-grow:1}div.quarto-about-trestles .about-contents h2,div.quarto-about-trestles .about-contents .h2{border-bottom:none}@media(min-width: 992px){div.quarto-about-trestles .about-contents{border-left:solid 1px #dee2e6;padding-left:1.5em}}div.quarto-about-trestles .about-contents main.content{margin-top:0}div.quarto-about-marquee{padding-bottom:1em}div.quarto-about-marquee .about-contents{display:flex;flex-direction:column}div.quarto-about-marquee .about-image{max-height:550px;margin-bottom:1.5em;object-fit:cover}div.quarto-about-marquee img.round{border-radius:50%}div.quarto-about-marquee img.rounded{border-radius:10px}div.quarto-about-marquee h2,div.quarto-about-marquee .h2{border-bottom:none}div.quarto-about-marquee .about-links{display:flex;justify-content:center;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-marquee .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-marquee .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-marquee .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-marquee .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-marquee .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-marquee .about-link:hover{color:blue}div.quarto-about-marquee .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-marquee .about-link{border:none}}div.quarto-about-broadside{display:flex;flex-direction:column;padding-bottom:1em}div.quarto-about-broadside .about-main{display:flex !important;padding-top:0 !important}@media(min-width: 992px){div.quarto-about-broadside .about-main{flex-direction:row;align-items:flex-start}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main{flex-direction:column}}@media(max-width: 991.98px){div.quarto-about-broadside .about-main .about-entity{flex-shrink:0;width:100%;height:450px;margin-bottom:1.5em;background-size:cover;background-repeat:no-repeat}}@media(min-width: 992px){div.quarto-about-broadside .about-main .about-entity{flex:0 10 50%;margin-right:1.5em;width:100%;height:100%;background-size:100%;background-repeat:no-repeat}}div.quarto-about-broadside .about-main .about-contents{padding-top:14px;flex:0 0 50%}div.quarto-about-broadside h2,div.quarto-about-broadside .h2{border-bottom:none}div.quarto-about-broadside .about-sep{margin-top:1.5em;width:60%;align-self:center}div.quarto-about-broadside .about-links{display:flex;justify-content:center;column-gap:20px;padding-top:1.5em}@media(min-width: 992px){div.quarto-about-broadside .about-links{flex-direction:row;column-gap:.8em;row-gap:15px;flex-wrap:wrap}}@media(max-width: 991.98px){div.quarto-about-broadside .about-links{flex-direction:column;row-gap:1em;width:100%;padding-bottom:1.5em}}div.quarto-about-broadside .about-link{color:#4e5862;text-decoration:none;border:solid 1px}@media(min-width: 992px){div.quarto-about-broadside .about-link{font-size:.8em;padding:.25em .5em;border-radius:4px}}@media(max-width: 991.98px){div.quarto-about-broadside .about-link{font-size:1.1em;padding:.5em .5em;text-align:center;border-radius:6px}}div.quarto-about-broadside .about-link:hover{color:blue}div.quarto-about-broadside .about-link i.bi{margin-right:.15em}@media(min-width: 992px){div.quarto-about-broadside .about-link{border:none}}.tippy-box[data-theme~=quarto]{background-color:#fff;border:solid 1px #dee2e6;border-radius:.25rem;color:#212529;font-size:.875rem}.tippy-box[data-theme~=quarto]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=quarto]>.tippy-arrow:after,.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{content:"";position:absolute;z-index:-1}.tippy-box[data-theme~=quarto]>.tippy-arrow:after{border-color:rgba(0,0,0,0);border-style:solid}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-6px}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-6px}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-6px}.tippy-box[data-placement^=left]>.tippy-arrow:before{right:-6px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-arrow:after{border-top-color:#dee2e6;border-width:7px 7px 0;top:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow>svg{top:16px}.tippy-box[data-theme~=quarto][data-placement^=top]>.tippy-svg-arrow:after{top:17px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff;bottom:16px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-arrow:after{border-bottom-color:#dee2e6;border-width:0 7px 7px;bottom:17px;left:1px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:15px}.tippy-box[data-theme~=quarto][data-placement^=bottom]>.tippy-svg-arrow:after{bottom:17px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-arrow:after{border-left-color:#dee2e6;border-width:7px 0 7px 7px;left:17px;top:1px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow>svg{left:11px}.tippy-box[data-theme~=quarto][data-placement^=left]>.tippy-svg-arrow:after{left:12px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff;right:16px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-arrow:after{border-width:7px 7px 7px 0;right:17px;top:1px;border-right-color:#dee2e6}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow>svg{right:11px}.tippy-box[data-theme~=quarto][data-placement^=right]>.tippy-svg-arrow:after{right:12px}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow{fill:#212529}.tippy-box[data-theme~=quarto]>.tippy-svg-arrow:after{background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMCA2czEuNzk2LS4wMTMgNC42Ny0zLjYxNUM1Ljg1MS45IDYuOTMuMDA2IDggMGMxLjA3LS4wMDYgMi4xNDguODg3IDMuMzQzIDIuMzg1QzE0LjIzMyA2LjAwNSAxNiA2IDE2IDZIMHoiIGZpbGw9InJnYmEoMCwgOCwgMTYsIDAuMikiLz48L3N2Zz4=);background-size:16px 6px;width:16px;height:6px}.top-right{position:absolute;top:1em;right:1em}.visually-hidden{border:0;clip:rect(0 0 0 0);height:auto;margin:0;overflow:hidden;padding:0;position:absolute;width:1px;white-space:nowrap}.hidden{display:none !important}.zindex-bottom{z-index:-1 !important}figure.figure{display:block}.quarto-layout-panel{margin-bottom:1em}.quarto-layout-panel>figure{width:100%}.quarto-layout-panel>figure>figcaption,.quarto-layout-panel>.panel-caption{margin-top:10pt}.quarto-layout-panel>.table-caption{margin-top:0px}.table-caption p{margin-bottom:.5em}.quarto-layout-row{display:flex;flex-direction:row;align-items:flex-start}.quarto-layout-valign-top{align-items:flex-start}.quarto-layout-valign-bottom{align-items:flex-end}.quarto-layout-valign-center{align-items:center}.quarto-layout-cell{position:relative;margin-right:20px}.quarto-layout-cell:last-child{margin-right:0}.quarto-layout-cell figure,.quarto-layout-cell>p{margin:.2em}.quarto-layout-cell img{max-width:100%}.quarto-layout-cell .html-widget{width:100% !important}.quarto-layout-cell div figure p{margin:0}.quarto-layout-cell figure{display:block;margin-inline-start:0;margin-inline-end:0}.quarto-layout-cell table{display:inline-table}.quarto-layout-cell-subref figcaption,figure .quarto-layout-row figure figcaption{text-align:center;font-style:italic}.quarto-figure{position:relative;margin-bottom:1em}.quarto-figure>figure{width:100%;margin-bottom:0}.quarto-figure-left>figure>p,.quarto-figure-left>figure>div{text-align:left}.quarto-figure-center>figure>p,.quarto-figure-center>figure>div{text-align:center}.quarto-figure-right>figure>p,.quarto-figure-right>figure>div{text-align:right}.quarto-figure>figure>div.cell-annotation,.quarto-figure>figure>div code{text-align:left}figure>p:empty{display:none}figure>p:first-child{margin-top:0;margin-bottom:0}figure>figcaption.quarto-float-caption-bottom{margin-bottom:.5em}figure>figcaption.quarto-float-caption-top{margin-top:.5em}div[id^=tbl-]{position:relative}.quarto-figure>.anchorjs-link{position:absolute;top:.6em;right:.5em}div[id^=tbl-]>.anchorjs-link{position:absolute;top:.7em;right:.3em}.quarto-figure:hover>.anchorjs-link,div[id^=tbl-]:hover>.anchorjs-link,h2:hover>.anchorjs-link,.h2:hover>.anchorjs-link,h3:hover>.anchorjs-link,.h3:hover>.anchorjs-link,h4:hover>.anchorjs-link,.h4:hover>.anchorjs-link,h5:hover>.anchorjs-link,.h5:hover>.anchorjs-link,h6:hover>.anchorjs-link,.h6:hover>.anchorjs-link,.reveal-anchorjs-link>.anchorjs-link{opacity:1}#title-block-header{margin-block-end:1rem;position:relative;margin-top:-1px}#title-block-header .abstract{margin-block-start:1rem}#title-block-header .abstract .abstract-title{font-weight:600}#title-block-header a{text-decoration:none}#title-block-header .author,#title-block-header .date,#title-block-header .doi{margin-block-end:.2rem}#title-block-header .quarto-title-block>div{display:flex}#title-block-header .quarto-title-block>div>h1,#title-block-header .quarto-title-block>div>.h1{flex-grow:1}#title-block-header .quarto-title-block>div>button{flex-shrink:0;height:2.25rem;margin-top:0}@media(min-width: 992px){#title-block-header .quarto-title-block>div>button{margin-top:5px}}tr.header>th>p:last-of-type{margin-bottom:0px}table,table.table{margin-top:.5rem;margin-bottom:.5rem}caption,.table-caption{padding-top:.5rem;padding-bottom:.5rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-top{margin-top:.5rem;margin-bottom:.25rem;text-align:center}figure.quarto-float-tbl figcaption.quarto-float-caption-bottom{padding-top:.25rem;margin-bottom:.5rem;text-align:center}.utterances{max-width:none;margin-left:-8px}iframe{margin-bottom:1em}details{margin-bottom:1em}details[show]{margin-bottom:0}details>summary{color:#6c757d}details>summary>p:only-child{display:inline}pre.sourceCode,code.sourceCode{position:relative}p code:not(.sourceCode){white-space:pre-wrap}code{white-space:pre}@media print{code{white-space:pre-wrap}}pre>code{display:block}pre>code.sourceCode{white-space:pre-wrap}pre>code.sourceCode>span>a:first-child::before{text-decoration:none}pre.code-overflow-wrap>code.sourceCode{white-space:pre-wrap}pre.code-overflow-scroll>code.sourceCode{white-space:pre}code a:any-link{color:inherit;text-decoration:none}code a:hover{color:inherit;text-decoration:underline}ul.task-list{padding-left:1em}[data-tippy-root]{display:inline-block}.tippy-content .footnote-back{display:none}.footnote-back{margin-left:.2em}.tippy-content{overflow-x:auto}.quarto-embedded-source-code{display:none}.quarto-unresolved-ref{font-weight:600}.quarto-cover-image{max-width:35%;float:right;margin-left:30px}.cell-output-display .widget-subarea{margin-bottom:1em}.cell-output-display:not(.no-overflow-x),.knitsql-table:not(.no-overflow-x){overflow-x:auto}.panel-input{margin-bottom:1em}.panel-input>div,.panel-input>div>div{display:inline-block;vertical-align:top;padding-right:12px}.panel-input>p:last-child{margin-bottom:0}.layout-sidebar{margin-bottom:1em}.layout-sidebar .tab-content{border:none}.tab-content>.page-columns.active{display:grid}div.sourceCode>iframe{width:100%;height:300px;margin-bottom:-0.5em}a{text-underline-offset:3px}div.ansi-escaped-output{font-family:monospace;display:block}/*! * * ansi colors from IPython notebook's * -*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-fg{color:#282c36}.ansi-black-intense-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-fg{color:#b22b31}.ansi-red-intense-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-fg{color:#007427}.ansi-green-intense-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-fg{color:#b27d12}.ansi-yellow-intense-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-fg{color:#0065ca}.ansi-blue-intense-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-fg{color:#a03196}.ansi-magenta-intense-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-fg{color:#258f8f}.ansi-cyan-intense-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-fg{color:#a1a6b2}.ansi-white-intense-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #ffffff;--quarto-body-color: #212529;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:transparent;border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:transparent}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:transparent}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] 35px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 35px [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end] 3em [body-end] 50px [body-end-outset] minmax(0px, 250px) [page-end-inset] 50px [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 175px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 200px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(50px, 100px) [page-start-inset] 50px [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(0px, 100px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 50px [page-start-inset] minmax(50px, 150px) [body-start-outset] 50px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(50px, 150px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(25px, 50px) [page-start-inset] minmax(50px, 150px) [body-start-outset] minmax(25px, 50px) [body-start] 1.5em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end] minmax(25px, 50px) [body-end-outset] minmax(50px, 150px) [page-end-inset] minmax(25px, 50px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1200px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 35px [page-start-inset] minmax(0px, 145px) [body-start-outset] 35px [body-start] 1.5em [body-content-start] minmax(450px, calc(750px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc( 1000px - 3em )) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(800px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(25px, 50px) [page-end-inset] 50px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 35px [body-end-outset] minmax(75px, 145px) [page-end-inset] 35px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(750px - 3em)) [body-content-end] 1.5em [body-end] 50px [body-end-outset] minmax(75px, 150px) [page-end-inset] 25px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}.zindex-content{z-index:998;transform:translate3d(0, 0, 0)}.zindex-modal{z-index:1055;transform:translate3d(0, 0, 0)}.zindex-over-content{z-index:999;transform:translate3d(0, 0, 0)}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside,.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside,.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;transform:translate3d(0, 0, 0)}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{margin-top:2rem;margin-bottom:1rem}h1.title,.title.h1{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3,h4,.h4{margin-top:1.5rem}.header-section-number{color:#5a6570}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,caption,.figure-caption{font-size:1rem}.panel-caption,.figure-caption,figcaption{color:#5a6570}.table-caption,caption{color:#212529}.quarto-layout-cell[data-ref-parent] caption{color:#5a6570}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#5a6570;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:1em}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(233,236,239,.65);border:1px solid rgba(233,236,239,.65);border-radius:.25rem}pre.sourceCode{background-color:transparent}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#5a6570}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p code:not(.sourceCode),li code:not(.sourceCode){background-color:#f6f6f6;padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode){background-color:transparent;padding:0}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:transparent;transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.toc-left>*,.sidebar.margin-sidebar>*{padding-top:.5em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem;font-weight:400;margin-bottom:.5rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar nav[role=doc-toc] ul{padding-left:0;list-style:none;font-size:.875rem;font-weight:300}.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid #0d6efd;color:#0d6efd !important}.sidebar nav[role=doc-toc] ul>li>a.active{border-left:1px solid #0d6efd;color:#0d6efd !important}kbd,.kbd{color:#212529;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}div.hanging-indent{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.table a{word-break:break-word}.table>:not(:first-child){border-top-width:1px;border-top-color:#dee2e6}.table>thead{border-bottom:1px solid currentColor}.table>tbody{border-top:1px solid #dee2e6}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout.callout-captioned .callout-body{margin-top:.2em}.callout:not(.no-icon).callout-captioned.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-captioned>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default div.callout-body>:first-child{margin-top:.5em}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-captioned .callout-body>:last-child:not(.sourceCode),.callout.callout-captioned .callout-body>div>:last-child:not(.sourceCode){margin-bottom:.5rem}.callout:not(.callout-captioned) .callout-body>:first-child,.callout:not(.callout-captioned) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-captioned) .callout-body>:last-child,.callout:not(.callout-captioned) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-caption-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#0d6efd}div.callout-note.callout-style-default>.callout-header{background-color:#e7f1ff}div.callout-note:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#198754}div.callout-tip.callout-style-default>.callout-header{background-color:#e8f3ee}div.callout-tip:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#fff9e6}div.callout-warning:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#fd7e14}div.callout-caution.callout-style-default>.callout-header{background-color:#fff2e8}div.callout-caution:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#dc3545}div.callout-important.callout-style-default>.callout-header{background-color:#fcebec}div.callout-important:not(.callout-captioned) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-captioned .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}@media(min-width: 992px){.navbar .quarto-color-scheme-toggle{padding-left:.5rem;padding-right:.5rem}}@media(max-width: 767.98px){.navbar .quarto-color-scheme-toggle{padding-left:0;padding-right:0;padding-bottom:.5em}}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.navbar-collapse .quarto-color-scheme-toggle{padding-left:.6rem;padding-right:0;margin-top:-12px}.sidebar-navigation{padding-left:20px}.sidebar-navigation .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.sidebar-tools-main .quarto-color-scheme-toggle .bi::before{padding-top:.2rem;margin-bottom:-0.2rem}.navbar .quarto-color-scheme-toggle .bi::before{padding-top:7px;margin-bottom:-7px;padding-left:2px;margin-right:2px}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#212529}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{color:#fefefe;background-color:#6c757d;border-color:#6c757d}.btn.btn-quarto:hover,div.cell-output-display .btn-quarto:hover{color:#fefefe;background-color:#828a91;border-color:#7b838a}.btn-check:focus+.btn.btn-quarto,.btn.btn-quarto:focus,.btn-check:focus+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:focus{color:#fefefe;background-color:#828a91;border-color:#7b838a;box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn-check:checked+.btn.btn-quarto,.btn-check:active+.btn.btn-quarto,.btn.btn-quarto:active,.btn.btn-quarto.active,.show>.btn.btn-quarto.dropdown-toggle,.btn-check:checked+div.cell-output-display .btn-quarto,.btn-check:active+div.cell-output-display .btn-quarto,div.cell-output-display .btn-quarto:active,div.cell-output-display .btn-quarto.active,.show>div.cell-output-display .btn-quarto.dropdown-toggle{color:#000;background-color:#899197;border-color:#7b838a}.btn-check:checked+.btn.btn-quarto:focus,.btn-check:active+.btn.btn-quarto:focus,.btn.btn-quarto:active:focus,.btn.btn-quarto.active:focus,.show>.btn.btn-quarto.dropdown-toggle:focus,.btn-check:checked+div.cell-output-display .btn-quarto:focus,.btn-check:active+div.cell-output-display .btn-quarto:focus,div.cell-output-display .btn-quarto:active:focus,div.cell-output-display .btn-quarto.active:focus,.show>div.cell-output-display .btn-quarto.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,144,.5)}.btn.btn-quarto:disabled,.btn.btn-quarto.disabled,div.cell-output-display .btn-quarto:disabled,div.cell-output-display .btn-quarto.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#0d6efd;color:#fdfeff}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfeff}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner,body.nav-sidebar .quarto-title-banner{display:none}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;transform:translate3d(0, 0, 0)}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;transform:translate3d(0, 0, 0);margin-bottom:1em}}.quarto-video{margin-bottom:1em}a.external:after{display:inline-block;height:.75rem;width:.75rem;margin-bottom:.15em;margin-left:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file,.code-with-filename .code-with-filename-file pre{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file,.quarto-dark .code-with-filename .code-with-filename-file pre{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfeff;background:#0d6efd}.quarto-title-banner .code-tools-button{color:#97cbff}.quarto-title-banner .code-tools-button:hover{color:#fdfeff}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}main.quarto-banner-title-block section:first-of-type h2:first-of-type,main.quarto-banner-title-block section:first-of-type .h2:first-of-type,main.quarto-banner-title-block section:first-of-type h3:first-of-type,main.quarto-banner-title-block section:first-of-type .h3:first-of-type,main.quarto-banner-title-block section:first-of-type h4:first-of-type,main.quarto-banner-title-block section:first-of-type .h4:first-of-type{margin-top:0}.quarto-title .quarto-categories{display:flex;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:repeat(2, 1fr)}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-5px}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents a{color:#212529}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.7em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .description .abstract-title,#title-block-header.quarto-title-block.default .abstract .abstract-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:1fr 1fr}/*# sourceMappingURL=603954f6f730b7a48ae583e90c07e56e.css.map */ +* we also add `bright-[color]-` synonyms for the `-[color]-intense` classes since +* that seems to be what ansi_up emits +* +*/.ansi-black-fg{color:#3e424d}.ansi-black-bg{background-color:#3e424d}.ansi-black-intense-black,.ansi-bright-black-fg{color:#282c36}.ansi-black-intense-black,.ansi-bright-black-bg{background-color:#282c36}.ansi-red-fg{color:#e75c58}.ansi-red-bg{background-color:#e75c58}.ansi-red-intense-red,.ansi-bright-red-fg{color:#b22b31}.ansi-red-intense-red,.ansi-bright-red-bg{background-color:#b22b31}.ansi-green-fg{color:#00a250}.ansi-green-bg{background-color:#00a250}.ansi-green-intense-green,.ansi-bright-green-fg{color:#007427}.ansi-green-intense-green,.ansi-bright-green-bg{background-color:#007427}.ansi-yellow-fg{color:#ddb62b}.ansi-yellow-bg{background-color:#ddb62b}.ansi-yellow-intense-yellow,.ansi-bright-yellow-fg{color:#b27d12}.ansi-yellow-intense-yellow,.ansi-bright-yellow-bg{background-color:#b27d12}.ansi-blue-fg{color:#208ffb}.ansi-blue-bg{background-color:#208ffb}.ansi-blue-intense-blue,.ansi-bright-blue-fg{color:#0065ca}.ansi-blue-intense-blue,.ansi-bright-blue-bg{background-color:#0065ca}.ansi-magenta-fg{color:#d160c4}.ansi-magenta-bg{background-color:#d160c4}.ansi-magenta-intense-magenta,.ansi-bright-magenta-fg{color:#a03196}.ansi-magenta-intense-magenta,.ansi-bright-magenta-bg{background-color:#a03196}.ansi-cyan-fg{color:#60c6c8}.ansi-cyan-bg{background-color:#60c6c8}.ansi-cyan-intense-cyan,.ansi-bright-cyan-fg{color:#258f8f}.ansi-cyan-intense-cyan,.ansi-bright-cyan-bg{background-color:#258f8f}.ansi-white-fg{color:#c5c1b4}.ansi-white-bg{background-color:#c5c1b4}.ansi-white-intense-white,.ansi-bright-white-fg{color:#a1a6b2}.ansi-white-intense-white,.ansi-bright-white-bg{background-color:#a1a6b2}.ansi-default-inverse-fg{color:#fff}.ansi-default-inverse-bg{background-color:#000}.ansi-bold{font-weight:bold}.ansi-underline{text-decoration:underline}:root{--quarto-body-bg: #ffffff;--quarto-body-color: #212529;--quarto-text-muted: #6c757d;--quarto-border-color: #dee2e6;--quarto-border-width: 1px;--quarto-border-radius: 0.25rem}table.gt_table{color:var(--quarto-body-color);font-size:1em;width:100%;background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_column_spanner_outer{color:var(--quarto-body-color);background-color:rgba(0,0,0,0);border-top-width:inherit;border-bottom-width:inherit;border-color:var(--quarto-border-color)}table.gt_table th.gt_col_heading{color:var(--quarto-body-color);font-weight:bold;background-color:rgba(0,0,0,0)}table.gt_table thead.gt_col_headings{border-bottom:1px solid currentColor;border-top-width:inherit;border-top-color:var(--quarto-border-color)}table.gt_table thead.gt_col_headings:not(:first-child){border-top-width:1px;border-top-color:var(--quarto-border-color)}table.gt_table td.gt_row{border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-width:0px}table.gt_table tbody.gt_table_body{border-top-width:1px;border-bottom-width:1px;border-bottom-color:var(--quarto-border-color);border-top-color:currentColor}div.columns{display:initial;gap:initial}div.column{display:inline-block;overflow-x:initial;vertical-align:top;width:50%}.code-annotation-tip-content{word-wrap:break-word}.code-annotation-container-hidden{display:none !important}dl.code-annotation-container-grid{display:grid;grid-template-columns:min-content auto}dl.code-annotation-container-grid dt{grid-column:1}dl.code-annotation-container-grid dd{grid-column:2}pre.sourceCode.code-annotation-code{padding-right:0}code.sourceCode .code-annotation-anchor{z-index:100;position:relative;float:right;background-color:rgba(0,0,0,0)}input[type=checkbox]{margin-right:.5ch}:root{--mermaid-bg-color: #ffffff;--mermaid-edge-color: #6c757d;--mermaid-node-fg-color: #212529;--mermaid-fg-color: #212529;--mermaid-fg-color--lighter: #383f45;--mermaid-fg-color--lightest: #4e5862;--mermaid-font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;--mermaid-label-bg-color: #ffffff;--mermaid-label-fg-color: #0d6efd;--mermaid-node-bg-color: rgba(13, 110, 253, 0.1);--mermaid-node-fg-color: #212529}@media print{:root{font-size:11pt}#quarto-sidebar,#TOC,.nav-page{display:none}.page-columns .content{grid-column-start:page-start}.fixed-top{position:relative}.panel-caption,.figure-caption,figcaption{color:#666}}.code-copy-button{position:absolute;top:0;right:0;border:0;margin-top:5px;margin-right:5px;background-color:rgba(0,0,0,0);z-index:3}.code-copy-button:focus{outline:none}.code-copy-button-tooltip{font-size:.75em}pre.sourceCode:hover>.code-copy-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}pre.sourceCode:hover>.code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}pre.sourceCode:hover>.code-copy-button-checked:hover>.bi::before{background-image:url('data:image/svg+xml,')}main ol ol,main ul ul,main ol ul,main ul ol{margin-bottom:1em}ul>li:not(:has(>p))>ul,ol>li:not(:has(>p))>ul,ul>li:not(:has(>p))>ol,ol>li:not(:has(>p))>ol{margin-bottom:0}ul>li:not(:has(>p))>ul>li:has(>p),ol>li:not(:has(>p))>ul>li:has(>p),ul>li:not(:has(>p))>ol>li:has(>p),ol>li:not(:has(>p))>ol>li:has(>p){margin-top:1rem}body{margin:0}main.page-columns>header>h1.title,main.page-columns>header>.title.h1{margin-bottom:0}@media(min-width: 992px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 42px [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(500px, calc(950px - 3em)) [body-content-end] 1.5em [body-end] 42px [body-end-outset] minmax(90px, 174px) [page-end-inset] 42px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 42px [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(500px, calc(950px - 3em)) [body-content-end] 1.5em [body-end] 42px [body-end-outset] 42px [page-end-inset page-end] 5fr [screen-end-inset] 1.5em}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset] 42px [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(500px, calc(950px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(0px, 240px) [page-end-inset] 42px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(60px, 120px) [page-start-inset] 60px [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(500px, calc(950px - 3em)) [body-content-end] 3em [body-end] 60px [body-end-outset] minmax(0px, 300px) [page-end-inset] minmax(60px, 120px) [page-end] 1fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 42px [page-start-inset] minmax(0px, 210px) [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(450px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(0px, 240px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 42px [page-start-inset] minmax(0px, 210px) [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(450px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(0px, 240px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(30px, 60px) [page-start-inset] minmax(60px, 180px) [body-start-outset] minmax(30px, 60px) [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] minmax(30px, 60px) [body-end-outset] minmax(60px, 180px) [page-end-inset] minmax(30px, 60px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(60px, 120px) [page-start-inset] 60px [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(500px, calc(1100px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(60px, 120px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(60px, 120px) [page-start-inset] 60px [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(500px, calc(1100px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 60px [page-start-inset] minmax(60px, 180px) [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(60px, 120px) [page-start-inset] 60px [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(450px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(0px, 240px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start] minmax(60px, 120px) [page-start-inset] 60px [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(500px, calc(1100px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(0px, 240px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 60px [page-start-inset] minmax(60px, 180px) [body-start-outset] 60px [body-start] 1.5em [body-content-start] minmax(450px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(60px, 180px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] minmax(30px, 60px) [page-start-inset] minmax(60px, 180px) [body-start-outset] minmax(30px, 60px) [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] minmax(30px, 60px) [body-end-outset] minmax(60px, 180px) [page-end-inset] minmax(30px, 60px) [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 991.98px){body .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] 42px [body-end-outset] minmax(90px, 174px) [page-end-inset] 42px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.fullcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.slimcontent:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end] 42px [body-end-outset] minmax(90px, 174px) [page-end-inset] 42px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.listing:not(.floating):not(.docked) .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset] 5fr [body-start] 1.5em [body-content-start] minmax(500px, calc(1350px - 3em)) [body-content-end body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 42px [page-start-inset] minmax(0px, 174px) [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(450px, calc(900px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start] 42px [page-start-inset] minmax(0px, 174px) [body-start-outset] 42px [body-start] 1.5em [body-content-start] minmax(450px, calc(900px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1.5em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(90px, 180px) [page-end-inset] 30px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(30px, 60px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(1100px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(900px - 3em)) [body-content-end] 1.5em [body-end body-end-outset page-end-inset page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.docked.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(30px, 60px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.docked.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(30px, 60px) [page-end-inset] 60px [page-end] 5fr [screen-end-inset] 1.5em [screen-end]}body.floating.slimcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 42px [body-end-outset] minmax(90px, 174px) [page-end-inset] 42px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}body.floating.listing .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset] 5fr [page-start page-start-inset body-start-outset body-start] 1em [body-content-start] minmax(500px, calc(850px - 3em)) [body-content-end] 1.5em [body-end] 60px [body-end-outset] minmax(90px, 180px) [page-end-inset] 30px [page-end] 4fr [screen-end-inset] 1.5em [screen-end]}}@media(max-width: 767.98px){body .page-columns,body.fullcontent:not(.floating):not(.docked) .page-columns,body.slimcontent:not(.floating):not(.docked) .page-columns,body.docked .page-columns,body.docked.slimcontent .page-columns,body.docked.fullcontent .page-columns,body.floating .page-columns,body.floating.slimcontent .page-columns,body.floating.fullcontent .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}body:not(.floating):not(.docked) .page-columns.toc-left .page-columns{display:grid;gap:0;grid-template-columns:[screen-start] 1.5em [screen-start-inset page-start page-start-inset body-start-outset body-start body-content-start] minmax(0px, 1fr) [body-content-end body-end body-end-outset page-end-inset page-end screen-end-inset] 1.5em [screen-end]}nav[role=doc-toc]{display:none}}body,.page-row-navigation{grid-template-rows:[page-top] max-content [contents-top] max-content [contents-bottom] max-content [page-bottom]}.page-rows-contents{grid-template-rows:[content-top] minmax(max-content, 1fr) [content-bottom] minmax(60px, max-content) [page-bottom]}.page-full{grid-column:screen-start/screen-end !important}.page-columns>*{grid-column:body-content-start/body-content-end}.page-columns.column-page>*{grid-column:page-start/page-end}.page-columns.column-page-left .page-columns.page-full>*,.page-columns.column-page-left>*{grid-column:page-start/body-content-end}.page-columns.column-page-right .page-columns.page-full>*,.page-columns.column-page-right>*{grid-column:body-content-start/page-end}.page-rows{grid-auto-rows:auto}.header{grid-column:screen-start/screen-end;grid-row:page-top/contents-top}#quarto-content{padding:0;grid-column:screen-start/screen-end;grid-row:contents-top/contents-bottom}body.floating .sidebar.sidebar-navigation{grid-column:page-start/body-start;grid-row:content-top/page-bottom}body.docked .sidebar.sidebar-navigation{grid-column:screen-start/body-start;grid-row:content-top/page-bottom}.sidebar.toc-left{grid-column:page-start/body-start;grid-row:content-top/page-bottom}.sidebar.margin-sidebar{grid-column:body-end/page-end;grid-row:content-top/page-bottom}.page-columns .content{grid-column:body-content-start/body-content-end;grid-row:content-top/content-bottom;align-content:flex-start}.page-columns .page-navigation{grid-column:body-content-start/body-content-end;grid-row:content-bottom/page-bottom}.page-columns .footer{grid-column:screen-start/screen-end;grid-row:contents-bottom/page-bottom}.page-columns .column-body{grid-column:body-content-start/body-content-end}.page-columns .column-body-fullbleed{grid-column:body-start/body-end}.page-columns .column-body-outset{grid-column:body-start-outset/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset table{background:#fff}.page-columns .column-body-outset-left{grid-column:body-start-outset/body-content-end;z-index:998;opacity:.999}.page-columns .column-body-outset-left table{background:#fff}.page-columns .column-body-outset-right{grid-column:body-content-start/body-end-outset;z-index:998;opacity:.999}.page-columns .column-body-outset-right table{background:#fff}.page-columns .column-page{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-page table{background:#fff}.page-columns .column-page-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset table{background:#fff}.page-columns .column-page-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-inset-left table{background:#fff}.page-columns .column-page-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-page-inset-right figcaption table{background:#fff}.page-columns .column-page-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-page-left table{background:#fff}.page-columns .column-page-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-page-right figcaption table{background:#fff}#quarto-content.page-columns #quarto-margin-sidebar,#quarto-content.page-columns #quarto-sidebar{z-index:1}@media(max-width: 991.98px){#quarto-content.page-columns #quarto-margin-sidebar.collapse,#quarto-content.page-columns #quarto-sidebar.collapse,#quarto-content.page-columns #quarto-margin-sidebar.collapsing,#quarto-content.page-columns #quarto-sidebar.collapsing{z-index:1055}}#quarto-content.page-columns main.column-page,#quarto-content.page-columns main.column-page-right,#quarto-content.page-columns main.column-page-left{z-index:0}.page-columns .column-screen-inset{grid-column:screen-start-inset/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:screen-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/screen-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:screen-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:screen-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/screen-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:screen-start/screen-end;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}.zindex-content{z-index:998;opacity:.999}.zindex-modal{z-index:1055;opacity:.999}.zindex-over-content{z-index:999;opacity:.999}img.img-fluid.column-screen,img.img-fluid.column-screen-inset-shaded,img.img-fluid.column-screen-inset,img.img-fluid.column-screen-inset-left,img.img-fluid.column-screen-inset-right,img.img-fluid.column-screen-left,img.img-fluid.column-screen-right{width:100%}@media(min-width: 992px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.column-sidebar{grid-column:page-start/body-start !important;z-index:998}.column-leftmargin{grid-column:screen-start-inset/body-start !important;z-index:998}.no-row-height{height:1em;overflow:visible}}@media(max-width: 991.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-end/page-end !important;z-index:998}.no-row-height{height:1em;overflow:visible}.page-columns.page-full{overflow:visible}.page-columns.toc-left .margin-caption,.page-columns.toc-left div.aside,.page-columns.toc-left aside:not(.footnotes):not(.sidebar),.page-columns.toc-left .column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.page-columns.toc-left .no-row-height{height:initial;overflow:initial}}@media(max-width: 767.98px){.margin-caption,div.aside,aside:not(.footnotes):not(.sidebar),.column-margin{grid-column:body-content-start/body-content-end !important;z-index:998;opacity:.999}.no-row-height{height:initial;overflow:initial}#quarto-margin-sidebar{display:none}#quarto-sidebar-toc-left{display:none}.hidden-sm{display:none}}.panel-grid{display:grid;grid-template-rows:repeat(1, 1fr);grid-template-columns:repeat(24, 1fr);gap:1em}.panel-grid .g-col-1{grid-column:auto/span 1}.panel-grid .g-col-2{grid-column:auto/span 2}.panel-grid .g-col-3{grid-column:auto/span 3}.panel-grid .g-col-4{grid-column:auto/span 4}.panel-grid .g-col-5{grid-column:auto/span 5}.panel-grid .g-col-6{grid-column:auto/span 6}.panel-grid .g-col-7{grid-column:auto/span 7}.panel-grid .g-col-8{grid-column:auto/span 8}.panel-grid .g-col-9{grid-column:auto/span 9}.panel-grid .g-col-10{grid-column:auto/span 10}.panel-grid .g-col-11{grid-column:auto/span 11}.panel-grid .g-col-12{grid-column:auto/span 12}.panel-grid .g-col-13{grid-column:auto/span 13}.panel-grid .g-col-14{grid-column:auto/span 14}.panel-grid .g-col-15{grid-column:auto/span 15}.panel-grid .g-col-16{grid-column:auto/span 16}.panel-grid .g-col-17{grid-column:auto/span 17}.panel-grid .g-col-18{grid-column:auto/span 18}.panel-grid .g-col-19{grid-column:auto/span 19}.panel-grid .g-col-20{grid-column:auto/span 20}.panel-grid .g-col-21{grid-column:auto/span 21}.panel-grid .g-col-22{grid-column:auto/span 22}.panel-grid .g-col-23{grid-column:auto/span 23}.panel-grid .g-col-24{grid-column:auto/span 24}.panel-grid .g-start-1{grid-column-start:1}.panel-grid .g-start-2{grid-column-start:2}.panel-grid .g-start-3{grid-column-start:3}.panel-grid .g-start-4{grid-column-start:4}.panel-grid .g-start-5{grid-column-start:5}.panel-grid .g-start-6{grid-column-start:6}.panel-grid .g-start-7{grid-column-start:7}.panel-grid .g-start-8{grid-column-start:8}.panel-grid .g-start-9{grid-column-start:9}.panel-grid .g-start-10{grid-column-start:10}.panel-grid .g-start-11{grid-column-start:11}.panel-grid .g-start-12{grid-column-start:12}.panel-grid .g-start-13{grid-column-start:13}.panel-grid .g-start-14{grid-column-start:14}.panel-grid .g-start-15{grid-column-start:15}.panel-grid .g-start-16{grid-column-start:16}.panel-grid .g-start-17{grid-column-start:17}.panel-grid .g-start-18{grid-column-start:18}.panel-grid .g-start-19{grid-column-start:19}.panel-grid .g-start-20{grid-column-start:20}.panel-grid .g-start-21{grid-column-start:21}.panel-grid .g-start-22{grid-column-start:22}.panel-grid .g-start-23{grid-column-start:23}@media(min-width: 576px){.panel-grid .g-col-sm-1{grid-column:auto/span 1}.panel-grid .g-col-sm-2{grid-column:auto/span 2}.panel-grid .g-col-sm-3{grid-column:auto/span 3}.panel-grid .g-col-sm-4{grid-column:auto/span 4}.panel-grid .g-col-sm-5{grid-column:auto/span 5}.panel-grid .g-col-sm-6{grid-column:auto/span 6}.panel-grid .g-col-sm-7{grid-column:auto/span 7}.panel-grid .g-col-sm-8{grid-column:auto/span 8}.panel-grid .g-col-sm-9{grid-column:auto/span 9}.panel-grid .g-col-sm-10{grid-column:auto/span 10}.panel-grid .g-col-sm-11{grid-column:auto/span 11}.panel-grid .g-col-sm-12{grid-column:auto/span 12}.panel-grid .g-col-sm-13{grid-column:auto/span 13}.panel-grid .g-col-sm-14{grid-column:auto/span 14}.panel-grid .g-col-sm-15{grid-column:auto/span 15}.panel-grid .g-col-sm-16{grid-column:auto/span 16}.panel-grid .g-col-sm-17{grid-column:auto/span 17}.panel-grid .g-col-sm-18{grid-column:auto/span 18}.panel-grid .g-col-sm-19{grid-column:auto/span 19}.panel-grid .g-col-sm-20{grid-column:auto/span 20}.panel-grid .g-col-sm-21{grid-column:auto/span 21}.panel-grid .g-col-sm-22{grid-column:auto/span 22}.panel-grid .g-col-sm-23{grid-column:auto/span 23}.panel-grid .g-col-sm-24{grid-column:auto/span 24}.panel-grid .g-start-sm-1{grid-column-start:1}.panel-grid .g-start-sm-2{grid-column-start:2}.panel-grid .g-start-sm-3{grid-column-start:3}.panel-grid .g-start-sm-4{grid-column-start:4}.panel-grid .g-start-sm-5{grid-column-start:5}.panel-grid .g-start-sm-6{grid-column-start:6}.panel-grid .g-start-sm-7{grid-column-start:7}.panel-grid .g-start-sm-8{grid-column-start:8}.panel-grid .g-start-sm-9{grid-column-start:9}.panel-grid .g-start-sm-10{grid-column-start:10}.panel-grid .g-start-sm-11{grid-column-start:11}.panel-grid .g-start-sm-12{grid-column-start:12}.panel-grid .g-start-sm-13{grid-column-start:13}.panel-grid .g-start-sm-14{grid-column-start:14}.panel-grid .g-start-sm-15{grid-column-start:15}.panel-grid .g-start-sm-16{grid-column-start:16}.panel-grid .g-start-sm-17{grid-column-start:17}.panel-grid .g-start-sm-18{grid-column-start:18}.panel-grid .g-start-sm-19{grid-column-start:19}.panel-grid .g-start-sm-20{grid-column-start:20}.panel-grid .g-start-sm-21{grid-column-start:21}.panel-grid .g-start-sm-22{grid-column-start:22}.panel-grid .g-start-sm-23{grid-column-start:23}}@media(min-width: 768px){.panel-grid .g-col-md-1{grid-column:auto/span 1}.panel-grid .g-col-md-2{grid-column:auto/span 2}.panel-grid .g-col-md-3{grid-column:auto/span 3}.panel-grid .g-col-md-4{grid-column:auto/span 4}.panel-grid .g-col-md-5{grid-column:auto/span 5}.panel-grid .g-col-md-6{grid-column:auto/span 6}.panel-grid .g-col-md-7{grid-column:auto/span 7}.panel-grid .g-col-md-8{grid-column:auto/span 8}.panel-grid .g-col-md-9{grid-column:auto/span 9}.panel-grid .g-col-md-10{grid-column:auto/span 10}.panel-grid .g-col-md-11{grid-column:auto/span 11}.panel-grid .g-col-md-12{grid-column:auto/span 12}.panel-grid .g-col-md-13{grid-column:auto/span 13}.panel-grid .g-col-md-14{grid-column:auto/span 14}.panel-grid .g-col-md-15{grid-column:auto/span 15}.panel-grid .g-col-md-16{grid-column:auto/span 16}.panel-grid .g-col-md-17{grid-column:auto/span 17}.panel-grid .g-col-md-18{grid-column:auto/span 18}.panel-grid .g-col-md-19{grid-column:auto/span 19}.panel-grid .g-col-md-20{grid-column:auto/span 20}.panel-grid .g-col-md-21{grid-column:auto/span 21}.panel-grid .g-col-md-22{grid-column:auto/span 22}.panel-grid .g-col-md-23{grid-column:auto/span 23}.panel-grid .g-col-md-24{grid-column:auto/span 24}.panel-grid .g-start-md-1{grid-column-start:1}.panel-grid .g-start-md-2{grid-column-start:2}.panel-grid .g-start-md-3{grid-column-start:3}.panel-grid .g-start-md-4{grid-column-start:4}.panel-grid .g-start-md-5{grid-column-start:5}.panel-grid .g-start-md-6{grid-column-start:6}.panel-grid .g-start-md-7{grid-column-start:7}.panel-grid .g-start-md-8{grid-column-start:8}.panel-grid .g-start-md-9{grid-column-start:9}.panel-grid .g-start-md-10{grid-column-start:10}.panel-grid .g-start-md-11{grid-column-start:11}.panel-grid .g-start-md-12{grid-column-start:12}.panel-grid .g-start-md-13{grid-column-start:13}.panel-grid .g-start-md-14{grid-column-start:14}.panel-grid .g-start-md-15{grid-column-start:15}.panel-grid .g-start-md-16{grid-column-start:16}.panel-grid .g-start-md-17{grid-column-start:17}.panel-grid .g-start-md-18{grid-column-start:18}.panel-grid .g-start-md-19{grid-column-start:19}.panel-grid .g-start-md-20{grid-column-start:20}.panel-grid .g-start-md-21{grid-column-start:21}.panel-grid .g-start-md-22{grid-column-start:22}.panel-grid .g-start-md-23{grid-column-start:23}}@media(min-width: 992px){.panel-grid .g-col-lg-1{grid-column:auto/span 1}.panel-grid .g-col-lg-2{grid-column:auto/span 2}.panel-grid .g-col-lg-3{grid-column:auto/span 3}.panel-grid .g-col-lg-4{grid-column:auto/span 4}.panel-grid .g-col-lg-5{grid-column:auto/span 5}.panel-grid .g-col-lg-6{grid-column:auto/span 6}.panel-grid .g-col-lg-7{grid-column:auto/span 7}.panel-grid .g-col-lg-8{grid-column:auto/span 8}.panel-grid .g-col-lg-9{grid-column:auto/span 9}.panel-grid .g-col-lg-10{grid-column:auto/span 10}.panel-grid .g-col-lg-11{grid-column:auto/span 11}.panel-grid .g-col-lg-12{grid-column:auto/span 12}.panel-grid .g-col-lg-13{grid-column:auto/span 13}.panel-grid .g-col-lg-14{grid-column:auto/span 14}.panel-grid .g-col-lg-15{grid-column:auto/span 15}.panel-grid .g-col-lg-16{grid-column:auto/span 16}.panel-grid .g-col-lg-17{grid-column:auto/span 17}.panel-grid .g-col-lg-18{grid-column:auto/span 18}.panel-grid .g-col-lg-19{grid-column:auto/span 19}.panel-grid .g-col-lg-20{grid-column:auto/span 20}.panel-grid .g-col-lg-21{grid-column:auto/span 21}.panel-grid .g-col-lg-22{grid-column:auto/span 22}.panel-grid .g-col-lg-23{grid-column:auto/span 23}.panel-grid .g-col-lg-24{grid-column:auto/span 24}.panel-grid .g-start-lg-1{grid-column-start:1}.panel-grid .g-start-lg-2{grid-column-start:2}.panel-grid .g-start-lg-3{grid-column-start:3}.panel-grid .g-start-lg-4{grid-column-start:4}.panel-grid .g-start-lg-5{grid-column-start:5}.panel-grid .g-start-lg-6{grid-column-start:6}.panel-grid .g-start-lg-7{grid-column-start:7}.panel-grid .g-start-lg-8{grid-column-start:8}.panel-grid .g-start-lg-9{grid-column-start:9}.panel-grid .g-start-lg-10{grid-column-start:10}.panel-grid .g-start-lg-11{grid-column-start:11}.panel-grid .g-start-lg-12{grid-column-start:12}.panel-grid .g-start-lg-13{grid-column-start:13}.panel-grid .g-start-lg-14{grid-column-start:14}.panel-grid .g-start-lg-15{grid-column-start:15}.panel-grid .g-start-lg-16{grid-column-start:16}.panel-grid .g-start-lg-17{grid-column-start:17}.panel-grid .g-start-lg-18{grid-column-start:18}.panel-grid .g-start-lg-19{grid-column-start:19}.panel-grid .g-start-lg-20{grid-column-start:20}.panel-grid .g-start-lg-21{grid-column-start:21}.panel-grid .g-start-lg-22{grid-column-start:22}.panel-grid .g-start-lg-23{grid-column-start:23}}@media(min-width: 1200px){.panel-grid .g-col-xl-1{grid-column:auto/span 1}.panel-grid .g-col-xl-2{grid-column:auto/span 2}.panel-grid .g-col-xl-3{grid-column:auto/span 3}.panel-grid .g-col-xl-4{grid-column:auto/span 4}.panel-grid .g-col-xl-5{grid-column:auto/span 5}.panel-grid .g-col-xl-6{grid-column:auto/span 6}.panel-grid .g-col-xl-7{grid-column:auto/span 7}.panel-grid .g-col-xl-8{grid-column:auto/span 8}.panel-grid .g-col-xl-9{grid-column:auto/span 9}.panel-grid .g-col-xl-10{grid-column:auto/span 10}.panel-grid .g-col-xl-11{grid-column:auto/span 11}.panel-grid .g-col-xl-12{grid-column:auto/span 12}.panel-grid .g-col-xl-13{grid-column:auto/span 13}.panel-grid .g-col-xl-14{grid-column:auto/span 14}.panel-grid .g-col-xl-15{grid-column:auto/span 15}.panel-grid .g-col-xl-16{grid-column:auto/span 16}.panel-grid .g-col-xl-17{grid-column:auto/span 17}.panel-grid .g-col-xl-18{grid-column:auto/span 18}.panel-grid .g-col-xl-19{grid-column:auto/span 19}.panel-grid .g-col-xl-20{grid-column:auto/span 20}.panel-grid .g-col-xl-21{grid-column:auto/span 21}.panel-grid .g-col-xl-22{grid-column:auto/span 22}.panel-grid .g-col-xl-23{grid-column:auto/span 23}.panel-grid .g-col-xl-24{grid-column:auto/span 24}.panel-grid .g-start-xl-1{grid-column-start:1}.panel-grid .g-start-xl-2{grid-column-start:2}.panel-grid .g-start-xl-3{grid-column-start:3}.panel-grid .g-start-xl-4{grid-column-start:4}.panel-grid .g-start-xl-5{grid-column-start:5}.panel-grid .g-start-xl-6{grid-column-start:6}.panel-grid .g-start-xl-7{grid-column-start:7}.panel-grid .g-start-xl-8{grid-column-start:8}.panel-grid .g-start-xl-9{grid-column-start:9}.panel-grid .g-start-xl-10{grid-column-start:10}.panel-grid .g-start-xl-11{grid-column-start:11}.panel-grid .g-start-xl-12{grid-column-start:12}.panel-grid .g-start-xl-13{grid-column-start:13}.panel-grid .g-start-xl-14{grid-column-start:14}.panel-grid .g-start-xl-15{grid-column-start:15}.panel-grid .g-start-xl-16{grid-column-start:16}.panel-grid .g-start-xl-17{grid-column-start:17}.panel-grid .g-start-xl-18{grid-column-start:18}.panel-grid .g-start-xl-19{grid-column-start:19}.panel-grid .g-start-xl-20{grid-column-start:20}.panel-grid .g-start-xl-21{grid-column-start:21}.panel-grid .g-start-xl-22{grid-column-start:22}.panel-grid .g-start-xl-23{grid-column-start:23}}@media(min-width: 1400px){.panel-grid .g-col-xxl-1{grid-column:auto/span 1}.panel-grid .g-col-xxl-2{grid-column:auto/span 2}.panel-grid .g-col-xxl-3{grid-column:auto/span 3}.panel-grid .g-col-xxl-4{grid-column:auto/span 4}.panel-grid .g-col-xxl-5{grid-column:auto/span 5}.panel-grid .g-col-xxl-6{grid-column:auto/span 6}.panel-grid .g-col-xxl-7{grid-column:auto/span 7}.panel-grid .g-col-xxl-8{grid-column:auto/span 8}.panel-grid .g-col-xxl-9{grid-column:auto/span 9}.panel-grid .g-col-xxl-10{grid-column:auto/span 10}.panel-grid .g-col-xxl-11{grid-column:auto/span 11}.panel-grid .g-col-xxl-12{grid-column:auto/span 12}.panel-grid .g-col-xxl-13{grid-column:auto/span 13}.panel-grid .g-col-xxl-14{grid-column:auto/span 14}.panel-grid .g-col-xxl-15{grid-column:auto/span 15}.panel-grid .g-col-xxl-16{grid-column:auto/span 16}.panel-grid .g-col-xxl-17{grid-column:auto/span 17}.panel-grid .g-col-xxl-18{grid-column:auto/span 18}.panel-grid .g-col-xxl-19{grid-column:auto/span 19}.panel-grid .g-col-xxl-20{grid-column:auto/span 20}.panel-grid .g-col-xxl-21{grid-column:auto/span 21}.panel-grid .g-col-xxl-22{grid-column:auto/span 22}.panel-grid .g-col-xxl-23{grid-column:auto/span 23}.panel-grid .g-col-xxl-24{grid-column:auto/span 24}.panel-grid .g-start-xxl-1{grid-column-start:1}.panel-grid .g-start-xxl-2{grid-column-start:2}.panel-grid .g-start-xxl-3{grid-column-start:3}.panel-grid .g-start-xxl-4{grid-column-start:4}.panel-grid .g-start-xxl-5{grid-column-start:5}.panel-grid .g-start-xxl-6{grid-column-start:6}.panel-grid .g-start-xxl-7{grid-column-start:7}.panel-grid .g-start-xxl-8{grid-column-start:8}.panel-grid .g-start-xxl-9{grid-column-start:9}.panel-grid .g-start-xxl-10{grid-column-start:10}.panel-grid .g-start-xxl-11{grid-column-start:11}.panel-grid .g-start-xxl-12{grid-column-start:12}.panel-grid .g-start-xxl-13{grid-column-start:13}.panel-grid .g-start-xxl-14{grid-column-start:14}.panel-grid .g-start-xxl-15{grid-column-start:15}.panel-grid .g-start-xxl-16{grid-column-start:16}.panel-grid .g-start-xxl-17{grid-column-start:17}.panel-grid .g-start-xxl-18{grid-column-start:18}.panel-grid .g-start-xxl-19{grid-column-start:19}.panel-grid .g-start-xxl-20{grid-column-start:20}.panel-grid .g-start-xxl-21{grid-column-start:21}.panel-grid .g-start-xxl-22{grid-column-start:22}.panel-grid .g-start-xxl-23{grid-column-start:23}}main{margin-top:1em;margin-bottom:1em}h1,.h1,h2,.h2{color:inherit;margin-top:2rem;margin-bottom:1rem;font-weight:600}h1.title,.title.h1{margin-top:0}main.content>section:first-of-type>h2:first-child,main.content>section:first-of-type>.h2:first-child{margin-top:0}h2,.h2{border-bottom:1px solid #dee2e6;padding-bottom:.5rem}h3,.h3{font-weight:600}h3,.h3,h4,.h4{opacity:.9;margin-top:1.5rem}h5,.h5,h6,.h6{opacity:.9}.header-section-number{color:#5a6570}.nav-link.active .header-section-number{color:inherit}mark,.mark{padding:0em}.panel-caption,.figure-caption,.subfigure-caption,.table-caption,figcaption,caption{font-size:.9rem;color:#5a6570}.quarto-layout-cell[data-ref-parent] caption{color:#5a6570}.column-margin figcaption,.margin-caption,div.aside,aside,.column-margin{color:#5a6570;font-size:.825rem}.panel-caption.margin-caption{text-align:inherit}.column-margin.column-container p{margin-bottom:0}.column-margin.column-container>*:not(.collapse):first-child{padding-bottom:.5em;display:block}.column-margin.column-container>*:not(.collapse):not(:first-child){padding-top:.5em;padding-bottom:.5em;display:block}.column-margin.column-container>*.collapse:not(.show){display:none}@media(min-width: 768px){.column-margin.column-container .callout-margin-content:first-child{margin-top:4.5em}.column-margin.column-container .callout-margin-content-simple:first-child{margin-top:3.5em}}.margin-caption>*{padding-top:.5em;padding-bottom:.5em}@media(max-width: 767.98px){.quarto-layout-row{flex-direction:column}}.nav-tabs .nav-item{margin-top:1px;cursor:pointer}.tab-content{margin-top:0px;border-left:#dee2e6 1px solid;border-right:#dee2e6 1px solid;border-bottom:#dee2e6 1px solid;margin-left:0;padding:1em;margin-bottom:1em}@media(max-width: 767.98px){.layout-sidebar{margin-left:0;margin-right:0}}.panel-sidebar,.panel-sidebar .form-control,.panel-input,.panel-input .form-control,.selectize-dropdown{font-size:.9rem}.panel-sidebar .form-control,.panel-input .form-control{padding-top:.1rem}.tab-pane div.sourceCode{margin-top:0px}.tab-pane>p{padding-top:0}.tab-pane>p:nth-child(1){padding-top:0}.tab-pane>p:last-child{margin-bottom:0}.tab-pane>pre:last-child{margin-bottom:0}.tab-content>.tab-pane:not(.active){display:none !important}div.sourceCode{background-color:rgba(192,192,192,.16);border:1px solid rgba(192,192,192,.16);border-radius:.25rem}pre.sourceCode{background-color:rgba(0,0,0,0)}pre.sourceCode{border:none;font-size:.875em;overflow:visible !important;padding:.4em}.callout pre.sourceCode{padding-left:0}div.sourceCode{overflow-y:hidden}.callout div.sourceCode{margin-left:initial}.blockquote{font-size:inherit;padding-left:1rem;padding-right:1.5rem;color:#5a6570}.blockquote h1:first-child,.blockquote .h1:first-child,.blockquote h2:first-child,.blockquote .h2:first-child,.blockquote h3:first-child,.blockquote .h3:first-child,.blockquote h4:first-child,.blockquote .h4:first-child,.blockquote h5:first-child,.blockquote .h5:first-child{margin-top:0}pre{background-color:initial;padding:initial;border:initial}p pre code:not(.sourceCode),li pre code:not(.sourceCode),pre code:not(.sourceCode){background-color:initial}p code:not(.sourceCode),li code:not(.sourceCode),td code:not(.sourceCode){background-color:rgba(192,192,192,.2);padding:.2em}nav p code:not(.sourceCode),nav li code:not(.sourceCode),nav td code:not(.sourceCode){background-color:rgba(0,0,0,0);padding:0}td code:not(.sourceCode){white-space:pre-wrap}#quarto-embedded-source-code-modal>.modal-dialog{max-width:1000px;padding-left:1.75rem;padding-right:1.75rem}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body{padding:0}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-body div.sourceCode{margin:0;padding:.2rem .2rem;border-radius:0px;border:none}#quarto-embedded-source-code-modal>.modal-dialog>.modal-content>.modal-header{padding:.7rem}.code-tools-button{font-size:1rem;padding:.15rem .15rem;margin-left:5px;color:#6c757d;background-color:rgba(0,0,0,0);transition:initial;cursor:pointer}.code-tools-button>.bi::before{display:inline-block;height:1rem;width:1rem;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:1rem 1rem}.code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button>.bi::before{background-image:url('data:image/svg+xml,')}#quarto-embedded-source-code-modal .code-copy-button-checked>.bi::before{background-image:url('data:image/svg+xml,')}.sidebar{will-change:top;transition:top 200ms linear;position:sticky;overflow-y:auto;padding-top:1.2em;max-height:100vh}.sidebar.toc-left,.sidebar.margin-sidebar{top:0px;padding-top:1em}.sidebar.quarto-banner-title-block-sidebar>*{padding-top:1.65em}figure .quarto-notebook-link{margin-top:.5em}.quarto-notebook-link{font-size:.75em;color:#6c757d;margin-bottom:1em;text-decoration:none;display:block}.quarto-notebook-link:hover{text-decoration:underline;color:blue}.quarto-notebook-link::before{display:inline-block;height:.75rem;width:.75rem;margin-bottom:0em;margin-right:.25em;content:"";vertical-align:-0.125em;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-size:.75rem .75rem}.toc-actions i.bi,.quarto-code-links i.bi,.quarto-other-links i.bi,.quarto-alternate-notebooks i.bi,.quarto-alternate-formats i.bi{margin-right:.4em;font-size:.8rem}.quarto-other-links-text-target .quarto-code-links i.bi,.quarto-other-links-text-target .quarto-other-links i.bi{margin-right:.2em}.quarto-other-formats-text-target .quarto-alternate-formats i.bi{margin-right:.1em}.toc-actions i.bi.empty,.quarto-code-links i.bi.empty,.quarto-other-links i.bi.empty,.quarto-alternate-notebooks i.bi.empty,.quarto-alternate-formats i.bi.empty{padding-left:1em}.quarto-notebook h2,.quarto-notebook .h2{border-bottom:none}.quarto-notebook .cell-container{display:flex}.quarto-notebook .cell-container .cell{flex-grow:4}.quarto-notebook .cell-container .cell-decorator{padding-top:1.5em;padding-right:1em;text-align:right}.quarto-notebook .cell-container.code-fold .cell-decorator{padding-top:3em}.quarto-notebook .cell-code code{white-space:pre-wrap}.quarto-notebook .cell .cell-output-stderr pre code,.quarto-notebook .cell .cell-output-stdout pre code{white-space:pre-wrap;overflow-wrap:anywhere}.toc-actions,.quarto-alternate-formats,.quarto-other-links,.quarto-code-links,.quarto-alternate-notebooks{padding-left:0em}.sidebar .toc-actions a,.sidebar .quarto-alternate-formats a,.sidebar .quarto-other-links a,.sidebar .quarto-code-links a,.sidebar .quarto-alternate-notebooks a,.sidebar nav[role=doc-toc] a{text-decoration:none}.sidebar .toc-actions a:hover,.sidebar .quarto-other-links a:hover,.sidebar .quarto-code-links a:hover,.sidebar .quarto-alternate-formats a:hover,.sidebar .quarto-alternate-notebooks a:hover{color:blue}.sidebar .toc-actions h2,.sidebar .toc-actions .h2,.sidebar .quarto-code-links h2,.sidebar .quarto-code-links .h2,.sidebar .quarto-other-links h2,.sidebar .quarto-other-links .h2,.sidebar .quarto-alternate-notebooks h2,.sidebar .quarto-alternate-notebooks .h2,.sidebar .quarto-alternate-formats h2,.sidebar .quarto-alternate-formats .h2,.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-weight:500;margin-bottom:.2rem;margin-top:.3rem;font-family:inherit;border-bottom:0;padding-bottom:0;padding-top:0px}.sidebar .toc-actions>h2,.sidebar .toc-actions>.h2,.sidebar .quarto-code-links>h2,.sidebar .quarto-code-links>.h2,.sidebar .quarto-other-links>h2,.sidebar .quarto-other-links>.h2,.sidebar .quarto-alternate-notebooks>h2,.sidebar .quarto-alternate-notebooks>.h2,.sidebar .quarto-alternate-formats>h2,.sidebar .quarto-alternate-formats>.h2{font-size:.8rem}.sidebar nav[role=doc-toc]>h2,.sidebar nav[role=doc-toc]>.h2{font-size:.875rem}.sidebar nav[role=doc-toc]>ul a{border-left:1px solid #e9ecef;padding-left:.6rem}.sidebar .toc-actions h2>ul a,.sidebar .toc-actions .h2>ul a,.sidebar .quarto-code-links h2>ul a,.sidebar .quarto-code-links .h2>ul a,.sidebar .quarto-other-links h2>ul a,.sidebar .quarto-other-links .h2>ul a,.sidebar .quarto-alternate-notebooks h2>ul a,.sidebar .quarto-alternate-notebooks .h2>ul a,.sidebar .quarto-alternate-formats h2>ul a,.sidebar .quarto-alternate-formats .h2>ul a{border-left:none;padding-left:.6rem}.sidebar .toc-actions ul a:empty,.sidebar .quarto-code-links ul a:empty,.sidebar .quarto-other-links ul a:empty,.sidebar .quarto-alternate-notebooks ul a:empty,.sidebar .quarto-alternate-formats ul a:empty,.sidebar nav[role=doc-toc]>ul a:empty{display:none}.sidebar .toc-actions ul,.sidebar .quarto-code-links ul,.sidebar .quarto-other-links ul,.sidebar .quarto-alternate-notebooks ul,.sidebar .quarto-alternate-formats ul{padding-left:0;list-style:none}.sidebar nav[role=doc-toc] ul{list-style:none;padding-left:0;list-style:none}.sidebar nav[role=doc-toc]>ul{margin-left:.45em}.quarto-margin-sidebar nav[role=doc-toc]{padding-left:.5em}.sidebar .toc-actions>ul,.sidebar .quarto-code-links>ul,.sidebar .quarto-other-links>ul,.sidebar .quarto-alternate-notebooks>ul,.sidebar .quarto-alternate-formats>ul{font-size:.8rem}.sidebar nav[role=doc-toc]>ul{font-size:.875rem}.sidebar .toc-actions ul li a,.sidebar .quarto-code-links ul li a,.sidebar .quarto-other-links ul li a,.sidebar .quarto-alternate-notebooks ul li a,.sidebar .quarto-alternate-formats ul li a,.sidebar nav[role=doc-toc]>ul li a{line-height:1.1rem;padding-bottom:.2rem;padding-top:.2rem;color:inherit}.sidebar nav[role=doc-toc] ul>li>ul>li>a{padding-left:1.2em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>a{padding-left:2.4em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>a{padding-left:3.6em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:4.8em}.sidebar nav[role=doc-toc] ul>li>ul>li>ul>li>ul>li>ul>li>ul>li>a{padding-left:6em}.sidebar nav[role=doc-toc] ul>li>a.active,.sidebar nav[role=doc-toc] ul>li>ul>li>a.active{border-left:1px solid blue;color:blue !important}.sidebar nav[role=doc-toc] ul>li>a:hover,.sidebar nav[role=doc-toc] ul>li>ul>li>a:hover{color:blue !important}kbd,.kbd{color:#212529;background-color:#f8f9fa;border:1px solid;border-radius:5px;border-color:#dee2e6}.quarto-appendix-contents div.hanging-indent{margin-left:0em}.quarto-appendix-contents div.hanging-indent div.csl-entry{margin-left:1em;text-indent:-1em}.citation a,.footnote-ref{text-decoration:none}.footnotes ol{padding-left:1em}.tippy-content>*{margin-bottom:.7em}.tippy-content>*:last-child{margin-bottom:0}.callout{margin-top:1.25rem;margin-bottom:1.25rem;border-radius:.25rem;overflow-wrap:break-word}.callout .callout-title-container{overflow-wrap:anywhere}.callout.callout-style-simple{padding:.4em .7em;border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout.callout-style-default{border-left:5px solid;border-right:1px solid #dee2e6;border-top:1px solid #dee2e6;border-bottom:1px solid #dee2e6}.callout .callout-body-container{flex-grow:1}.callout.callout-style-simple .callout-body{font-size:.9rem;font-weight:400}.callout.callout-style-default .callout-body{font-size:.9rem;font-weight:400}.callout:not(.no-icon).callout-titled.callout-style-simple .callout-body{padding-left:1.6em}.callout.callout-titled>.callout-header{padding-top:.2em;margin-bottom:-0.2em}.callout.callout-style-simple>div.callout-header{border-bottom:none;font-size:.9rem;font-weight:600;opacity:75%}.callout.callout-style-default>div.callout-header{border-bottom:none;font-weight:600;opacity:85%;font-size:.9rem;padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body{padding-left:.5em;padding-right:.5em}.callout.callout-style-default .callout-body>:first-child{padding-top:.5rem;margin-top:0}.callout>div.callout-header[data-bs-toggle=collapse]{cursor:pointer}.callout.callout-style-default .callout-header[aria-expanded=false],.callout.callout-style-default .callout-header[aria-expanded=true]{padding-top:0px;margin-bottom:0px;align-items:center}.callout.callout-titled .callout-body>:last-child:not(.sourceCode),.callout.callout-titled .callout-body>div>:last-child:not(.sourceCode){padding-bottom:.5rem;margin-bottom:0}.callout:not(.callout-titled) .callout-body>:first-child,.callout:not(.callout-titled) .callout-body>div>:first-child{margin-top:.25rem}.callout:not(.callout-titled) .callout-body>:last-child,.callout:not(.callout-titled) .callout-body>div>:last-child{margin-bottom:.2rem}.callout.callout-style-simple .callout-icon::before,.callout.callout-style-simple .callout-toggle::before{height:1rem;width:1rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.callout.callout-style-default .callout-icon::before,.callout.callout-style-default .callout-toggle::before{height:.9rem;width:.9rem;display:inline-block;content:"";background-repeat:no-repeat;background-size:.9rem .9rem}.callout.callout-style-default .callout-toggle::before{margin-top:5px}.callout .callout-btn-toggle .callout-toggle::before{transition:transform .2s linear}.callout .callout-header[aria-expanded=false] .callout-toggle::before{transform:rotate(-90deg)}.callout .callout-header[aria-expanded=true] .callout-toggle::before{transform:none}.callout.callout-style-simple:not(.no-icon) div.callout-icon-container{padding-top:.2em;padding-right:.55em}.callout.callout-style-default:not(.no-icon) div.callout-icon-container{padding-top:.1em;padding-right:.35em}.callout.callout-style-default:not(.no-icon) div.callout-title-container{margin-top:-1px}.callout.callout-style-default.callout-caution:not(.no-icon) div.callout-icon-container{padding-top:.3em;padding-right:.35em}.callout>.callout-body>.callout-icon-container>.no-icon,.callout>.callout-header>.callout-icon-container>.no-icon{display:none}div.callout.callout{border-left-color:#6c757d}div.callout.callout-style-default>.callout-header{background-color:#6c757d}div.callout-note.callout{border-left-color:#0d6efd}div.callout-note.callout-style-default>.callout-header{background-color:#e7f1ff}div.callout-note:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-note .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-tip.callout{border-left-color:#198754}div.callout-tip.callout-style-default>.callout-header{background-color:#e8f3ee}div.callout-tip:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-tip .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-warning.callout{border-left-color:#ffc107}div.callout-warning.callout-style-default>.callout-header{background-color:#fff9e6}div.callout-warning:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-warning .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-caution.callout{border-left-color:#fd7e14}div.callout-caution.callout-style-default>.callout-header{background-color:#fff2e8}div.callout-caution:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-caution .callout-toggle::before{background-image:url('data:image/svg+xml,')}div.callout-important.callout{border-left-color:#dc3545}div.callout-important.callout-style-default>.callout-header{background-color:#fcebec}div.callout-important:not(.callout-titled) .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important.callout-titled .callout-icon::before{background-image:url('data:image/svg+xml,');}div.callout-important .callout-toggle::before{background-image:url('data:image/svg+xml,')}.quarto-toggle-container{display:flex;align-items:center}.quarto-reader-toggle .bi::before,.quarto-color-scheme-toggle .bi::before{display:inline-block;height:1rem;width:1rem;content:"";background-repeat:no-repeat;background-size:1rem 1rem}.sidebar-navigation{padding-left:20px}.navbar{background-color:#517699;color:#fdfefe}.navbar .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.navbar .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle:not(.alternate) .bi::before{background-image:url('data:image/svg+xml,')}.sidebar-navigation .quarto-color-scheme-toggle.alternate .bi::before{background-image:url('data:image/svg+xml,')}.quarto-sidebar-toggle{border-color:#dee2e6;border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem;border-style:solid;border-width:1px;overflow:hidden;border-top-width:0px;padding-top:0px !important}.quarto-sidebar-toggle-title{cursor:pointer;padding-bottom:2px;margin-left:.25em;text-align:center;font-weight:400;font-size:.775em}#quarto-content .quarto-sidebar-toggle{background:#fafafa}#quarto-content .quarto-sidebar-toggle-title{color:#212529}.quarto-sidebar-toggle-icon{color:#dee2e6;margin-right:.5em;float:right;transition:transform .2s ease}.quarto-sidebar-toggle-icon::before{padding-top:5px}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-icon{transform:rotate(-180deg)}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-title{border-bottom:solid #dee2e6 1px}.quarto-sidebar-toggle-contents{background-color:#fff;padding-right:10px;padding-left:10px;margin-top:0px !important;transition:max-height .5s ease}.quarto-sidebar-toggle.expanded .quarto-sidebar-toggle-contents{padding-top:1em;padding-bottom:10px}@media(max-width: 767.98px){.sidebar-menu-container{padding-bottom:5em}}.quarto-sidebar-toggle:not(.expanded) .quarto-sidebar-toggle-contents{padding-top:0px !important;padding-bottom:0px}nav[role=doc-toc]{z-index:1020}#quarto-sidebar>*,nav[role=doc-toc]>*{transition:opacity .1s ease,border .1s ease}#quarto-sidebar.slow>*,nav[role=doc-toc].slow>*{transition:opacity .4s ease,border .4s ease}.quarto-color-scheme-toggle:not(.alternate).top-right .bi::before{background-image:url('data:image/svg+xml,')}.quarto-color-scheme-toggle.alternate.top-right .bi::before{background-image:url('data:image/svg+xml,')}#quarto-appendix.default{border-top:1px solid #dee2e6}#quarto-appendix.default{background-color:#fff;padding-top:1.5em;margin-top:2em;z-index:998}#quarto-appendix.default .quarto-appendix-heading{margin-top:0;line-height:1.4em;font-weight:600;opacity:.9;border-bottom:none;margin-bottom:0}#quarto-appendix.default .footnotes ol,#quarto-appendix.default .footnotes ol li>p:last-of-type,#quarto-appendix.default .quarto-appendix-contents>p:last-of-type{margin-bottom:0}#quarto-appendix.default .footnotes ol{margin-left:.5em}#quarto-appendix.default .quarto-appendix-secondary-label{margin-bottom:.4em}#quarto-appendix.default .quarto-appendix-bibtex{font-size:.7em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-bibtex code.sourceCode{white-space:pre-wrap}#quarto-appendix.default .quarto-appendix-citeas{font-size:.9em;padding:1em;border:solid 1px #dee2e6;margin-bottom:1em}#quarto-appendix.default .quarto-appendix-heading{font-size:1em !important}#quarto-appendix.default *[role=doc-endnotes]>ol,#quarto-appendix.default .quarto-appendix-contents>*:not(h2):not(.h2){font-size:.9em}#quarto-appendix.default section{padding-bottom:1.5em}#quarto-appendix.default section *[role=doc-endnotes],#quarto-appendix.default section>*:not(a){opacity:.9;word-wrap:break-word}.btn.btn-quarto,div.cell-output-display .btn-quarto{--bs-btn-color: #fefefe;--bs-btn-bg: #6c757d;--bs-btn-border-color: #6c757d;--bs-btn-hover-color: #fefefe;--bs-btn-hover-bg: #828a91;--bs-btn-hover-border-color: #7b838a;--bs-btn-focus-shadow-rgb: 130, 138, 144;--bs-btn-active-color: #000;--bs-btn-active-bg: #899197;--bs-btn-active-border-color: #7b838a;--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);--bs-btn-disabled-color: #ffffff;--bs-btn-disabled-bg: #6c757d;--bs-btn-disabled-border-color: #6c757d}nav.quarto-secondary-nav.color-navbar{background-color:#517699;color:#fdfefe}nav.quarto-secondary-nav.color-navbar h1,nav.quarto-secondary-nav.color-navbar .h1,nav.quarto-secondary-nav.color-navbar .quarto-btn-toggle{color:#fdfefe}@media(max-width: 991.98px){body.nav-sidebar .quarto-title-banner{margin-bottom:0;padding-bottom:1em}body.nav-sidebar #title-block-header{margin-block-end:0}}p.subtitle{margin-top:.25em;margin-bottom:.5em}code a:any-link{color:inherit;text-decoration-color:#6c757d}/*! light */div.observablehq table thead tr th{background-color:var(--bs-body-bg)}input,button,select,optgroup,textarea{background-color:var(--bs-body-bg)}.code-annotated .code-copy-button{margin-right:1.25em;margin-top:0;padding-bottom:0;padding-top:3px}.code-annotation-gutter-bg{background-color:#fff}.code-annotation-gutter{background-color:rgba(192,192,192,.16)}.code-annotation-gutter,.code-annotation-gutter-bg{height:100%;width:calc(20px + .5em);position:absolute;top:0;right:0}dl.code-annotation-container-grid dt{margin-right:1em;margin-top:.25rem}dl.code-annotation-container-grid dt{font-family:"Fira Mono";color:#383f45;border:solid #383f45 1px;border-radius:50%;height:22px;width:22px;line-height:22px;font-size:11px;text-align:center;vertical-align:middle;text-decoration:none}dl.code-annotation-container-grid dt[data-target-cell]{cursor:pointer}dl.code-annotation-container-grid dt[data-target-cell].code-annotation-active{color:#fff;border:solid #aaa 1px;background-color:#aaa}pre.code-annotation-code{padding-top:0;padding-bottom:0}pre.code-annotation-code code{z-index:3}#code-annotation-line-highlight-gutter{width:100%;border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}#code-annotation-line-highlight{margin-left:-4em;width:calc(100% + 4em);border-top:solid rgba(170,170,170,.2666666667) 1px;border-bottom:solid rgba(170,170,170,.2666666667) 1px;z-index:2;background-color:rgba(170,170,170,.1333333333)}code.sourceCode .code-annotation-anchor.code-annotation-active{background-color:var(--quarto-hl-normal-color, #aaaaaa);border:solid var(--quarto-hl-normal-color, #aaaaaa) 1px;color:silver;font-weight:bolder}code.sourceCode .code-annotation-anchor{font-family:"Fira Mono";color:var(--quarto-hl-co-color);border:solid var(--quarto-hl-co-color) 1px;border-radius:50%;height:18px;width:18px;font-size:9px;margin-top:2px}code.sourceCode button.code-annotation-anchor{padding:2px;user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none}code.sourceCode a.code-annotation-anchor{line-height:18px;text-align:center;vertical-align:middle;cursor:default;text-decoration:none}@media print{.page-columns .column-screen-inset{grid-column:page-start-inset/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset table{background:#fff}.page-columns .column-screen-inset-left{grid-column:page-start-inset/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-inset-left table{background:#fff}.page-columns .column-screen-inset-right{grid-column:body-content-start/page-end-inset;z-index:998;opacity:.999}.page-columns .column-screen-inset-right table{background:#fff}.page-columns .column-screen{grid-column:page-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen table{background:#fff}.page-columns .column-screen-left{grid-column:page-start/body-content-end;z-index:998;opacity:.999}.page-columns .column-screen-left table{background:#fff}.page-columns .column-screen-right{grid-column:body-content-start/page-end;z-index:998;opacity:.999}.page-columns .column-screen-right table{background:#fff}.page-columns .column-screen-inset-shaded{grid-column:page-start-inset/page-end-inset;padding:1em;background:#f8f9fa;z-index:998;opacity:.999;margin-bottom:1em}}.quarto-video{margin-bottom:1em}.table{border-top:1px solid #d3d8dc;border-bottom:1px solid #d3d8dc}.table>thead{border-top-width:0;border-bottom:1px solid #9ba5ae}.table a{word-break:break-word}.table>:not(caption)>*>*{background-color:unset;color:unset}#quarto-document-content .crosstalk-input .checkbox input[type=checkbox],#quarto-document-content .crosstalk-input .checkbox-inline input[type=checkbox]{position:unset;margin-top:unset;margin-left:unset}#quarto-document-content .row{margin-left:unset;margin-right:unset}.quarto-xref{white-space:nowrap}a.external:after{content:"";background-image:url('data:image/svg+xml,');background-size:contain;background-repeat:no-repeat;background-position:center center;margin-left:.2em;padding-right:.75em}div.sourceCode code a.external:after{content:none}a.external:after:hover{cursor:pointer}.quarto-ext-icon{display:inline-block;font-size:.75em;padding-left:.3em}.code-with-filename .code-with-filename-file{margin-bottom:0;padding-bottom:2px;padding-top:2px;padding-left:.7em;border:var(--quarto-border-width) solid var(--quarto-border-color);border-radius:var(--quarto-border-radius);border-bottom:0;border-bottom-left-radius:0%;border-bottom-right-radius:0%}.code-with-filename div.sourceCode,.reveal .code-with-filename div.sourceCode{margin-top:0;border-top-left-radius:0%;border-top-right-radius:0%}.code-with-filename .code-with-filename-file pre{margin-bottom:0}.code-with-filename .code-with-filename-file{background-color:rgba(219,219,219,.8)}.quarto-dark .code-with-filename .code-with-filename-file{background-color:#555}.code-with-filename .code-with-filename-file strong{font-weight:400}.quarto-title-banner{margin-bottom:1em;color:#fdfefe;background:#517699}.quarto-title-banner a{color:#fdfefe}.quarto-title-banner h1,.quarto-title-banner .h1,.quarto-title-banner h2,.quarto-title-banner .h2{color:#fdfefe}.quarto-title-banner .code-tools-button{color:#b9dcdc}.quarto-title-banner .code-tools-button:hover{color:#fdfefe}.quarto-title-banner .code-tools-button>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .code-tools-button:hover>.bi::before{background-image:url('data:image/svg+xml,')}.quarto-title-banner .quarto-title .title{font-weight:600}.quarto-title-banner .quarto-categories{margin-top:.75em}@media(min-width: 992px){.quarto-title-banner{padding-top:2.5em;padding-bottom:2.5em}}@media(max-width: 991.98px){.quarto-title-banner{padding-top:1em;padding-bottom:1em}}@media(max-width: 767.98px){body.hypothesis-enabled #title-block-header>*{padding-right:20px}}main.quarto-banner-title-block>section:first-child>h2,main.quarto-banner-title-block>section:first-child>.h2,main.quarto-banner-title-block>section:first-child>h3,main.quarto-banner-title-block>section:first-child>.h3,main.quarto-banner-title-block>section:first-child>h4,main.quarto-banner-title-block>section:first-child>.h4{margin-top:0}.quarto-title .quarto-categories{display:flex;flex-wrap:wrap;row-gap:.5em;column-gap:.4em;padding-bottom:.5em;margin-top:.75em}.quarto-title .quarto-categories .quarto-category{padding:.25em .75em;font-size:.65em;text-transform:uppercase;border:solid 1px;border-radius:.25rem;opacity:.6}.quarto-title .quarto-categories .quarto-category a{color:inherit}.quarto-title-meta-container{display:grid;grid-template-columns:1fr auto}.quarto-title-meta-column-end{display:flex;flex-direction:column;padding-left:1em}.quarto-title-meta-column-end a .bi{margin-right:.3em}#title-block-header.quarto-title-block.default .quarto-title-meta{display:grid;grid-template-columns:minmax(max-content, 1fr) 1fr;grid-column-gap:1em}#title-block-header.quarto-title-block.default .quarto-title .title{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-author-orcid img{margin-top:-0.2em;height:.8em;width:.8em}#title-block-header.quarto-title-block.default .quarto-title-author-email{opacity:.7}#title-block-header.quarto-title-block.default .quarto-description p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p,#title-block-header.quarto-title-block.default .quarto-title-authors p,#title-block-header.quarto-title-block.default .quarto-title-affiliations p{margin-bottom:.1em}#title-block-header.quarto-title-block.default .quarto-title-meta-heading{text-transform:uppercase;margin-top:1em;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-contents{font-size:.9em}#title-block-header.quarto-title-block.default .quarto-title-meta-contents p.affiliation:last-of-type{margin-bottom:.1em}#title-block-header.quarto-title-block.default p.affiliation{margin-bottom:.1em}#title-block-header.quarto-title-block.default .keywords,#title-block-header.quarto-title-block.default .description,#title-block-header.quarto-title-block.default .abstract{margin-top:0}#title-block-header.quarto-title-block.default .keywords>p,#title-block-header.quarto-title-block.default .description>p,#title-block-header.quarto-title-block.default .abstract>p{font-size:.9em}#title-block-header.quarto-title-block.default .keywords>p:last-of-type,#title-block-header.quarto-title-block.default .description>p:last-of-type,#title-block-header.quarto-title-block.default .abstract>p:last-of-type{margin-bottom:0}#title-block-header.quarto-title-block.default .keywords .block-title,#title-block-header.quarto-title-block.default .description .block-title,#title-block-header.quarto-title-block.default .abstract .block-title{margin-top:1em;text-transform:uppercase;font-size:.8em;opacity:.8;font-weight:400}#title-block-header.quarto-title-block.default .quarto-title-meta-author{display:grid;grid-template-columns:minmax(max-content, 1fr) 1fr;grid-column-gap:1em}.quarto-title-tools-only{display:flex;justify-content:right} diff --git a/docs/site_libs/bootstrap/bootstrap.min.js b/docs/site_libs/bootstrap/bootstrap.min.js index cc0a255..e8f21f7 100644 --- a/docs/site_libs/bootstrap/bootstrap.min.js +++ b/docs/site_libs/bootstrap/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v5.1.3 (https://getbootstrap.com/) - * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Bootstrap v5.3.1 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t=new Map,e={set(e,i,n){t.has(e)||t.set(e,new Map);const s=t.get(e);s.has(i)||0===s.size?s.set(i,n):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(s.keys())[0]}.`)},get:(e,i)=>t.has(e)&&t.get(e).get(i)||null,remove(e,i){if(!t.has(e))return;const n=t.get(e);n.delete(i),0===n.size&&t.delete(e)}},i="transitionend",n=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),s=t=>{t.dispatchEvent(new Event(i))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(n(t)):null,a=t=>{if(!o(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},l=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),c=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?c(t.parentNode):null},h=()=>{},d=t=>{t.offsetHeight},u=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,f=[],p=()=>"rtl"===document.documentElement.dir,m=t=>{var e;e=()=>{const e=u();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(f.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of f)t()})),f.push(e)):e()},g=(t,e=[],i=t)=>"function"==typeof t?t(...e):i,_=(t,e,n=!0)=>{if(!n)return void g(t);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let r=!1;const a=({target:n})=>{n===e&&(r=!0,e.removeEventListener(i,a),g(t))};e.addEventListener(i,a),setTimeout((()=>{r||s(e)}),o)},b=(t,e,i,n)=>{const s=t.length;let o=t.indexOf(e);return-1===o?!i&&n?t[s-1]:t[0]:(o+=i?1:-1,n&&(o=(o+s)%s),t[Math.max(0,Math.min(o,s-1))])},v=/[^.]*(?=\..*)\.|.*/,y=/\..*/,w=/::\d+$/,A={};let E=1;const T={mouseenter:"mouseover",mouseleave:"mouseout"},C=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function O(t,e){return e&&`${e}::${E++}`||t.uidEvent||E++}function x(t){const e=O(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function k(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function L(t,e,i){const n="string"==typeof e,s=n?i:e||i;let o=I(t);return C.has(o)||(o=t),[n,s,o]}function S(t,e,i,n,s){if("string"!=typeof e||!t)return;let[o,r,a]=L(e,i,n);if(e in T){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=x(t),c=l[a]||(l[a]={}),h=k(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=O(r,e.replace(v,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return P(s,{delegateTarget:r}),n.oneOff&&N.off(t,s.type,e,i),i.apply(r,[s])}}(t,i,r):function(t,e){return function i(n){return P(n,{delegateTarget:t}),i.oneOff&&N.off(t,n.type,e),e.apply(t,[n])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function D(t,e,i,n,s){const o=k(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function $(t,e,i,n){const s=e[i]||{};for(const[o,r]of Object.entries(s))o.includes(n)&&D(t,e,i,r.callable,r.delegationSelector)}function I(t){return t=t.replace(y,""),T[t]||t}const N={on(t,e,i,n){S(t,e,i,n,!1)},one(t,e,i,n){S(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=L(e,i,n),a=r!==e,l=x(t),c=l[r]||{},h=e.startsWith(".");if(void 0===o){if(h)for(const i of Object.keys(l))$(t,l,i,e.slice(1));for(const[i,n]of Object.entries(c)){const s=i.replace(w,"");a&&!e.includes(s)||D(t,l,r,n.callable,n.delegationSelector)}}else{if(!Object.keys(c).length)return;D(t,l,r,o,s?i:null)}},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=u();let s=null,o=!0,r=!0,a=!1;e!==I(e)&&n&&(s=n.Event(e,i),n(t).trigger(s),o=!s.isPropagationStopped(),r=!s.isImmediatePropagationStopped(),a=s.isDefaultPrevented());const l=P(new Event(e,{bubbles:o,cancelable:!0}),i);return a&&l.preventDefault(),r&&t.dispatchEvent(l),l.defaultPrevented&&s&&s.preventDefault(),l}};function P(t,e={}){for(const[i,n]of Object.entries(e))try{t[i]=n}catch(e){Object.defineProperty(t,i,{configurable:!0,get:()=>n})}return t}function M(t){if("true"===t)return!0;if("false"===t)return!1;if(t===Number(t).toString())return Number(t);if(""===t||"null"===t)return null;if("string"!=typeof t)return t;try{return JSON.parse(decodeURIComponent(t))}catch(e){return t}}function j(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}const F={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${j(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${j(e)}`)},getDataAttributes(t){if(!t)return{};const e={},i=Object.keys(t.dataset).filter((t=>t.startsWith("bs")&&!t.startsWith("bsConfig")));for(const n of i){let i=n.replace(/^bs/,"");i=i.charAt(0).toLowerCase()+i.slice(1,i.length),e[i]=M(t.dataset[n])}return e},getDataAttribute:(t,e)=>M(t.getAttribute(`data-bs-${j(e)}`))};class H{static get Default(){return{}}static get DefaultType(){return{}}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}_getConfig(t){return t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t}_mergeConfigObj(t,e){const i=o(e)?F.getDataAttribute(e,"config"):{};return{...this.constructor.Default,..."object"==typeof i?i:{},...o(e)?F.getDataAttributes(e):{},..."object"==typeof t?t:{}}}_typeCheckConfig(t,e=this.constructor.DefaultType){for(const[n,s]of Object.entries(e)){const e=t[n],r=o(e)?"element":null==(i=e)?`${i}`:Object.prototype.toString.call(i).match(/\s([a-z]+)/i)[1].toLowerCase();if(!new RegExp(s).test(r))throw new TypeError(`${this.constructor.NAME.toUpperCase()}: Option "${n}" provided type "${r}" but expected type "${s}".`)}var i}}class W extends H{constructor(t,i){super(),(t=r(t))&&(this._element=t,this._config=this._getConfig(i),e.set(this._element,this.constructor.DATA_KEY,this))}dispose(){e.remove(this._element,this.constructor.DATA_KEY),N.off(this._element,this.constructor.EVENT_KEY);for(const t of Object.getOwnPropertyNames(this))this[t]=null}_queueCallback(t,e,i=!0){_(t,e,i)}_getConfig(t){return t=this._mergeConfigObj(t,this._element),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}static getInstance(t){return e.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.3.1"}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}static eventName(t){return`${t}${this.EVENT_KEY}`}}const B=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return n(e)},z={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode.closest(e);for(;n;)i.push(n),n=n.parentNode.closest(e);return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(",");return this.find(e,t).filter((t=>!l(t)&&a(t)))},getSelectorFromElement(t){const e=B(t);return e&&z.findOne(e)?e:null},getElementFromSelector(t){const e=B(t);return e?z.findOne(e):null},getMultipleElementsFromSelector(t){const e=B(t);return e?z.find(e):[]}},R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,n=t.NAME;N.on(document,i,`[data-bs-dismiss="${n}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),l(this))return;const s=z.getElementFromSelector(this)||this.closest(`.${n}`);t.getOrCreateInstance(s)[e]()}))},q=".bs.alert",V=`close${q}`,K=`closed${q}`;class Q extends W{static get NAME(){return"alert"}close(){if(N.trigger(this._element,V).defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),N.trigger(this._element,K),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=Q.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(Q,"close"),m(Q);const X='[data-bs-toggle="button"]';class Y extends W{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=Y.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}N.on(document,"click.bs.button.data-api",X,(t=>{t.preventDefault();const e=t.target.closest(X);Y.getOrCreateInstance(e).toggle()})),m(Y);const U=".bs.swipe",G=`touchstart${U}`,J=`touchmove${U}`,Z=`touchend${U}`,tt=`pointerdown${U}`,et=`pointerup${U}`,it={endCallback:null,leftCallback:null,rightCallback:null},nt={endCallback:"(function|null)",leftCallback:"(function|null)",rightCallback:"(function|null)"};class st extends H{constructor(t,e){super(),this._element=t,t&&st.isSupported()&&(this._config=this._getConfig(e),this._deltaX=0,this._supportPointerEvents=Boolean(window.PointerEvent),this._initEvents())}static get Default(){return it}static get DefaultType(){return nt}static get NAME(){return"swipe"}dispose(){N.off(this._element,U)}_start(t){this._supportPointerEvents?this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX):this._deltaX=t.touches[0].clientX}_end(t){this._eventIsPointerPenTouch(t)&&(this._deltaX=t.clientX-this._deltaX),this._handleSwipe(),g(this._config.endCallback)}_move(t){this._deltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this._deltaX}_handleSwipe(){const t=Math.abs(this._deltaX);if(t<=40)return;const e=t/this._deltaX;this._deltaX=0,e&&g(e>0?this._config.rightCallback:this._config.leftCallback)}_initEvents(){this._supportPointerEvents?(N.on(this._element,tt,(t=>this._start(t))),N.on(this._element,et,(t=>this._end(t))),this._element.classList.add("pointer-event")):(N.on(this._element,G,(t=>this._start(t))),N.on(this._element,J,(t=>this._move(t))),N.on(this._element,Z,(t=>this._end(t))))}_eventIsPointerPenTouch(t){return this._supportPointerEvents&&("pen"===t.pointerType||"touch"===t.pointerType)}static isSupported(){return"ontouchstart"in document.documentElement||navigator.maxTouchPoints>0}}const ot=".bs.carousel",rt=".data-api",at="next",lt="prev",ct="left",ht="right",dt=`slide${ot}`,ut=`slid${ot}`,ft=`keydown${ot}`,pt=`mouseenter${ot}`,mt=`mouseleave${ot}`,gt=`dragstart${ot}`,_t=`load${ot}${rt}`,bt=`click${ot}${rt}`,vt="carousel",yt="active",wt=".active",At=".carousel-item",Et=wt+At,Tt={ArrowLeft:ht,ArrowRight:ct},Ct={interval:5e3,keyboard:!0,pause:"hover",ride:!1,touch:!0,wrap:!0},Ot={interval:"(number|boolean)",keyboard:"boolean",pause:"(string|boolean)",ride:"(boolean|string)",touch:"boolean",wrap:"boolean"};class xt extends W{constructor(t,e){super(t,e),this._interval=null,this._activeElement=null,this._isSliding=!1,this.touchTimeout=null,this._swipeHelper=null,this._indicatorsElement=z.findOne(".carousel-indicators",this._element),this._addEventListeners(),this._config.ride===vt&&this.cycle()}static get Default(){return Ct}static get DefaultType(){return Ot}static get NAME(){return"carousel"}next(){this._slide(at)}nextWhenVisible(){!document.hidden&&a(this._element)&&this.next()}prev(){this._slide(lt)}pause(){this._isSliding&&s(this._element),this._clearInterval()}cycle(){this._clearInterval(),this._updateInterval(),this._interval=setInterval((()=>this.nextWhenVisible()),this._config.interval)}_maybeEnableCycle(){this._config.ride&&(this._isSliding?N.one(this._element,ut,(()=>this.cycle())):this.cycle())}to(t){const e=this._getItems();if(t>e.length-1||t<0)return;if(this._isSliding)return void N.one(this._element,ut,(()=>this.to(t)));const i=this._getItemIndex(this._getActive());if(i===t)return;const n=t>i?at:lt;this._slide(n,e[t])}dispose(){this._swipeHelper&&this._swipeHelper.dispose(),super.dispose()}_configAfterMerge(t){return t.defaultInterval=t.interval,t}_addEventListeners(){this._config.keyboard&&N.on(this._element,ft,(t=>this._keydown(t))),"hover"===this._config.pause&&(N.on(this._element,pt,(()=>this.pause())),N.on(this._element,mt,(()=>this._maybeEnableCycle()))),this._config.touch&&st.isSupported()&&this._addTouchEventListeners()}_addTouchEventListeners(){for(const t of z.find(".carousel-item img",this._element))N.on(t,gt,(t=>t.preventDefault()));const t={leftCallback:()=>this._slide(this._directionToOrder(ct)),rightCallback:()=>this._slide(this._directionToOrder(ht)),endCallback:()=>{"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((()=>this._maybeEnableCycle()),500+this._config.interval))}};this._swipeHelper=new st(this._element,t)}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=Tt[t.key];e&&(t.preventDefault(),this._slide(this._directionToOrder(e)))}_getItemIndex(t){return this._getItems().indexOf(t)}_setActiveIndicatorElement(t){if(!this._indicatorsElement)return;const e=z.findOne(wt,this._indicatorsElement);e.classList.remove(yt),e.removeAttribute("aria-current");const i=z.findOne(`[data-bs-slide-to="${t}"]`,this._indicatorsElement);i&&(i.classList.add(yt),i.setAttribute("aria-current","true"))}_updateInterval(){const t=this._activeElement||this._getActive();if(!t)return;const e=Number.parseInt(t.getAttribute("data-bs-interval"),10);this._config.interval=e||this._config.defaultInterval}_slide(t,e=null){if(this._isSliding)return;const i=this._getActive(),n=t===at,s=e||b(this._getItems(),i,n,this._config.wrap);if(s===i)return;const o=this._getItemIndex(s),r=e=>N.trigger(this._element,e,{relatedTarget:s,direction:this._orderToDirection(t),from:this._getItemIndex(i),to:o});if(r(dt).defaultPrevented)return;if(!i||!s)return;const a=Boolean(this._interval);this.pause(),this._isSliding=!0,this._setActiveIndicatorElement(o),this._activeElement=s;const l=n?"carousel-item-start":"carousel-item-end",c=n?"carousel-item-next":"carousel-item-prev";s.classList.add(c),d(s),i.classList.add(l),s.classList.add(l),this._queueCallback((()=>{s.classList.remove(l,c),s.classList.add(yt),i.classList.remove(yt,c,l),this._isSliding=!1,r(ut)}),i,this._isAnimated()),a&&this.cycle()}_isAnimated(){return this._element.classList.contains("slide")}_getActive(){return z.findOne(Et,this._element)}_getItems(){return z.find(At,this._element)}_clearInterval(){this._interval&&(clearInterval(this._interval),this._interval=null)}_directionToOrder(t){return p()?t===ct?lt:at:t===ct?at:lt}_orderToDirection(t){return p()?t===lt?ct:ht:t===lt?ht:ct}static jQueryInterface(t){return this.each((function(){const e=xt.getOrCreateInstance(this,t);if("number"!=typeof t){if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}else e.to(t)}))}}N.on(document,bt,"[data-bs-slide], [data-bs-slide-to]",(function(t){const e=z.getElementFromSelector(this);if(!e||!e.classList.contains(vt))return;t.preventDefault();const i=xt.getOrCreateInstance(e),n=this.getAttribute("data-bs-slide-to");return n?(i.to(n),void i._maybeEnableCycle()):"next"===F.getDataAttribute(this,"slide")?(i.next(),void i._maybeEnableCycle()):(i.prev(),void i._maybeEnableCycle())})),N.on(window,_t,(()=>{const t=z.find('[data-bs-ride="carousel"]');for(const e of t)xt.getOrCreateInstance(e)})),m(xt);const kt=".bs.collapse",Lt=`show${kt}`,St=`shown${kt}`,Dt=`hide${kt}`,$t=`hidden${kt}`,It=`click${kt}.data-api`,Nt="show",Pt="collapse",Mt="collapsing",jt=`:scope .${Pt} .${Pt}`,Ft='[data-bs-toggle="collapse"]',Ht={parent:null,toggle:!0},Wt={parent:"(null|element)",toggle:"boolean"};class Bt extends W{constructor(t,e){super(t,e),this._isTransitioning=!1,this._triggerArray=[];const i=z.find(Ft);for(const t of i){const e=z.getSelectorFromElement(t),i=z.find(e).filter((t=>t===this._element));null!==e&&i.length&&this._triggerArray.push(t)}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return Ht}static get DefaultType(){return Wt}static get NAME(){return"collapse"}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t=[];if(this._config.parent&&(t=this._getFirstLevelChildren(".collapse.show, .collapse.collapsing").filter((t=>t!==this._element)).map((t=>Bt.getOrCreateInstance(t,{toggle:!1})))),t.length&&t[0]._isTransitioning)return;if(N.trigger(this._element,Lt).defaultPrevented)return;for(const e of t)e.hide();const e=this._getDimension();this._element.classList.remove(Pt),this._element.classList.add(Mt),this._element.style[e]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const i=`scroll${e[0].toUpperCase()+e.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt,Nt),this._element.style[e]="",N.trigger(this._element,St)}),this._element,!0),this._element.style[e]=`${this._element[i]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(N.trigger(this._element,Dt).defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,d(this._element),this._element.classList.add(Mt),this._element.classList.remove(Pt,Nt);for(const t of this._triggerArray){const e=z.getElementFromSelector(t);e&&!this._isShown(e)&&this._addAriaAndCollapsedClass([t],!1)}this._isTransitioning=!0,this._element.style[t]="",this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(Mt),this._element.classList.add(Pt),N.trigger(this._element,$t)}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(Nt)}_configAfterMerge(t){return t.toggle=Boolean(t.toggle),t.parent=r(t.parent),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=this._getFirstLevelChildren(Ft);for(const e of t){const t=z.getElementFromSelector(e);t&&this._addAriaAndCollapsedClass([e],this._isShown(t))}}_getFirstLevelChildren(t){const e=z.find(jt,this._config.parent);return z.find(t,this._config.parent).filter((t=>!e.includes(t)))}_addAriaAndCollapsedClass(t,e){if(t.length)for(const i of t)i.classList.toggle("collapsed",!e),i.setAttribute("aria-expanded",e)}static jQueryInterface(t){const e={};return"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1),this.each((function(){const i=Bt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}N.on(document,It,Ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();for(const t of z.getMultipleElementsFromSelector(this))Bt.getOrCreateInstance(t,{toggle:!1}).toggle()})),m(Bt);var zt="top",Rt="bottom",qt="right",Vt="left",Kt="auto",Qt=[zt,Rt,qt,Vt],Xt="start",Yt="end",Ut="clippingParents",Gt="viewport",Jt="popper",Zt="reference",te=Qt.reduce((function(t,e){return t.concat([e+"-"+Xt,e+"-"+Yt])}),[]),ee=[].concat(Qt,[Kt]).reduce((function(t,e){return t.concat([e,e+"-"+Xt,e+"-"+Yt])}),[]),ie="beforeRead",ne="read",se="afterRead",oe="beforeMain",re="main",ae="afterMain",le="beforeWrite",ce="write",he="afterWrite",de=[ie,ne,se,oe,re,ae,le,ce,he];function ue(t){return t?(t.nodeName||"").toLowerCase():null}function fe(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function pe(t){return t instanceof fe(t).Element||t instanceof Element}function me(t){return t instanceof fe(t).HTMLElement||t instanceof HTMLElement}function ge(t){return"undefined"!=typeof ShadowRoot&&(t instanceof fe(t).ShadowRoot||t instanceof ShadowRoot)}const _e={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];me(s)&&ue(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});me(n)&&ue(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function be(t){return t.split("-")[0]}var ve=Math.max,ye=Math.min,we=Math.round;function Ae(){var t=navigator.userAgentData;return null!=t&&t.brands&&Array.isArray(t.brands)?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function Ee(){return!/^((?!chrome|android).)*safari/i.test(Ae())}function Te(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),s=1,o=1;e&&me(t)&&(s=t.offsetWidth>0&&we(n.width)/t.offsetWidth||1,o=t.offsetHeight>0&&we(n.height)/t.offsetHeight||1);var r=(pe(t)?fe(t):window).visualViewport,a=!Ee()&&i,l=(n.left+(a&&r?r.offsetLeft:0))/s,c=(n.top+(a&&r?r.offsetTop:0))/o,h=n.width/s,d=n.height/o;return{width:h,height:d,top:c,right:l+h,bottom:c+d,left:l,x:l,y:c}}function Ce(t){var e=Te(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Oe(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&ge(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function xe(t){return fe(t).getComputedStyle(t)}function ke(t){return["table","td","th"].indexOf(ue(t))>=0}function Le(t){return((pe(t)?t.ownerDocument:t.document)||window.document).documentElement}function Se(t){return"html"===ue(t)?t:t.assignedSlot||t.parentNode||(ge(t)?t.host:null)||Le(t)}function De(t){return me(t)&&"fixed"!==xe(t).position?t.offsetParent:null}function $e(t){for(var e=fe(t),i=De(t);i&&ke(i)&&"static"===xe(i).position;)i=De(i);return i&&("html"===ue(i)||"body"===ue(i)&&"static"===xe(i).position)?e:i||function(t){var e=/firefox/i.test(Ae());if(/Trident/i.test(Ae())&&me(t)&&"fixed"===xe(t).position)return null;var i=Se(t);for(ge(i)&&(i=i.host);me(i)&&["html","body"].indexOf(ue(i))<0;){var n=xe(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Ie(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function Ne(t,e,i){return ve(t,ye(e,i))}function Pe(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function Me(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const je={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=be(i.placement),l=Ie(a),c=[Vt,qt].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return Pe("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:Me(t,Qt))}(s.padding,i),d=Ce(o),u="y"===l?zt:Vt,f="y"===l?Rt:qt,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=$e(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,A=Ne(v,w,y),E=l;i.modifiersData[n]=((e={})[E]=A,e.centerOffset=A-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Oe(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Fe(t){return t.split("-")[1]}var He={top:"auto",right:"auto",bottom:"auto",left:"auto"};function We(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=t.isFixed,u=r.x,f=void 0===u?0:u,p=r.y,m=void 0===p?0:p,g="function"==typeof h?h({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var _=r.hasOwnProperty("x"),b=r.hasOwnProperty("y"),v=Vt,y=zt,w=window;if(c){var A=$e(i),E="clientHeight",T="clientWidth";A===fe(i)&&"static"!==xe(A=Le(i)).position&&"absolute"===a&&(E="scrollHeight",T="scrollWidth"),(s===zt||(s===Vt||s===qt)&&o===Yt)&&(y=Rt,m-=(d&&A===w&&w.visualViewport?w.visualViewport.height:A[E])-n.height,m*=l?1:-1),s!==Vt&&(s!==zt&&s!==Rt||o!==Yt)||(v=qt,f-=(d&&A===w&&w.visualViewport?w.visualViewport.width:A[T])-n.width,f*=l?1:-1)}var C,O=Object.assign({position:a},c&&He),x=!0===h?function(t,e){var i=t.x,n=t.y,s=e.devicePixelRatio||1;return{x:we(i*s)/s||0,y:we(n*s)/s||0}}({x:f,y:m},fe(i)):{x:f,y:m};return f=x.x,m=x.y,l?Object.assign({},O,((C={})[y]=b?"0":"",C[v]=_?"0":"",C.transform=(w.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",C)):Object.assign({},O,((e={})[y]=b?m+"px":"",e[v]=_?f+"px":"",e.transform="",e))}const Be={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:be(e.placement),variation:Fe(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,We(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,We(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var ze={passive:!0};const Re={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=fe(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,ze)})),a&&l.addEventListener("resize",i.update,ze),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,ze)})),a&&l.removeEventListener("resize",i.update,ze)}},data:{}};var qe={left:"right",right:"left",bottom:"top",top:"bottom"};function Ve(t){return t.replace(/left|right|bottom|top/g,(function(t){return qe[t]}))}var Ke={start:"end",end:"start"};function Qe(t){return t.replace(/start|end/g,(function(t){return Ke[t]}))}function Xe(t){var e=fe(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function Ye(t){return Te(Le(t)).left+Xe(t).scrollLeft}function Ue(t){var e=xe(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ge(t){return["html","body","#document"].indexOf(ue(t))>=0?t.ownerDocument.body:me(t)&&Ue(t)?t:Ge(Se(t))}function Je(t,e){var i;void 0===e&&(e=[]);var n=Ge(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=fe(n),r=s?[o].concat(o.visualViewport||[],Ue(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Je(Se(r)))}function Ze(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function ti(t,e,i){return e===Gt?Ze(function(t,e){var i=fe(t),n=Le(t),s=i.visualViewport,o=n.clientWidth,r=n.clientHeight,a=0,l=0;if(s){o=s.width,r=s.height;var c=Ee();(c||!c&&"fixed"===e)&&(a=s.offsetLeft,l=s.offsetTop)}return{width:o,height:r,x:a+Ye(t),y:l}}(t,i)):pe(e)?function(t,e){var i=Te(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):Ze(function(t){var e,i=Le(t),n=Xe(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ve(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ve(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+Ye(t),l=-n.scrollTop;return"rtl"===xe(s||i).direction&&(a+=ve(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Le(t)))}function ei(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?be(s):null,r=s?Fe(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case zt:e={x:a,y:i.y-n.height};break;case Rt:e={x:a,y:i.y+i.height};break;case qt:e={x:i.x+i.width,y:l};break;case Vt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?Ie(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case Xt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Yt:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ii(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.strategy,r=void 0===o?t.strategy:o,a=i.boundary,l=void 0===a?Ut:a,c=i.rootBoundary,h=void 0===c?Gt:c,d=i.elementContext,u=void 0===d?Jt:d,f=i.altBoundary,p=void 0!==f&&f,m=i.padding,g=void 0===m?0:m,_=Pe("number"!=typeof g?g:Me(g,Qt)),b=u===Jt?Zt:Jt,v=t.rects.popper,y=t.elements[p?b:u],w=function(t,e,i,n){var s="clippingParents"===e?function(t){var e=Je(Se(t)),i=["absolute","fixed"].indexOf(xe(t).position)>=0&&me(t)?$e(t):t;return pe(i)?e.filter((function(t){return pe(t)&&Oe(t,i)&&"body"!==ue(t)})):[]}(t):[].concat(e),o=[].concat(s,[i]),r=o[0],a=o.reduce((function(e,i){var s=ti(t,i,n);return e.top=ve(s.top,e.top),e.right=ye(s.right,e.right),e.bottom=ye(s.bottom,e.bottom),e.left=ve(s.left,e.left),e}),ti(t,r,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}(pe(y)?y:y.contextElement||Le(t.elements.popper),l,h,r),A=Te(t.elements.reference),E=ei({reference:A,element:v,strategy:"absolute",placement:s}),T=Ze(Object.assign({},v,E)),C=u===Jt?T:A,O={top:w.top-C.top+_.top,bottom:C.bottom-w.bottom+_.bottom,left:w.left-C.left+_.left,right:C.right-w.right+_.right},x=t.modifiersData.offset;if(u===Jt&&x){var k=x[s];Object.keys(O).forEach((function(t){var e=[qt,Rt].indexOf(t)>=0?1:-1,i=[zt,Rt].indexOf(t)>=0?"y":"x";O[t]+=k[i]*e}))}return O}function ni(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?ee:l,h=Fe(n),d=h?a?te:te.filter((function(t){return Fe(t)===h})):Qt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ii(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[be(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const si={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=be(g),b=l||(_!==g&&p?function(t){if(be(t)===Kt)return[];var e=Ve(t);return[Qe(t),e,Qe(e)]}(g):[Ve(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(be(i)===Kt?ni(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,A=new Map,E=!0,T=v[0],C=0;C=0,S=L?"width":"height",D=ii(e,{placement:O,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),$=L?k?qt:Vt:k?Rt:zt;y[S]>w[S]&&($=Ve($));var I=Ve($),N=[];if(o&&N.push(D[x]<=0),a&&N.push(D[$]<=0,D[I]<=0),N.every((function(t){return t}))){T=O,E=!1;break}A.set(O,N)}if(E)for(var P=function(t){var e=v.find((function(e){var i=A.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==P(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function oi(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function ri(t){return[zt,qt,Rt,Vt].some((function(e){return t[e]>=0}))}const ai={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ii(e,{elementContext:"reference"}),a=ii(e,{altBoundary:!0}),l=oi(r,n),c=oi(a,s,o),h=ri(l),d=ri(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},li={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=ee.reduce((function(t,i){return t[i]=function(t,e,i){var n=be(t),s=[Vt,zt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[Vt,qt].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},ci={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=ei({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},hi={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ii(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=be(e.placement),b=Fe(e.placement),v=!b,y=Ie(_),w="x"===y?"y":"x",A=e.modifiersData.popperOffsets,E=e.rects.reference,T=e.rects.popper,C="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,O="number"==typeof C?{mainAxis:C,altAxis:C}:Object.assign({mainAxis:0,altAxis:0},C),x=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,k={x:0,y:0};if(A){if(o){var L,S="y"===y?zt:Vt,D="y"===y?Rt:qt,$="y"===y?"height":"width",I=A[y],N=I+g[S],P=I-g[D],M=f?-T[$]/2:0,j=b===Xt?E[$]:T[$],F=b===Xt?-T[$]:-E[$],H=e.elements.arrow,W=f&&H?Ce(H):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},z=B[S],R=B[D],q=Ne(0,E[$],W[$]),V=v?E[$]/2-M-q-z-O.mainAxis:j-q-z-O.mainAxis,K=v?-E[$]/2+M+q+R+O.mainAxis:F+q+R+O.mainAxis,Q=e.elements.arrow&&$e(e.elements.arrow),X=Q?"y"===y?Q.clientTop||0:Q.clientLeft||0:0,Y=null!=(L=null==x?void 0:x[y])?L:0,U=I+K-Y,G=Ne(f?ye(N,I+V-Y-X):N,I,f?ve(P,U):P);A[y]=G,k[y]=G-I}if(a){var J,Z="x"===y?zt:Vt,tt="x"===y?Rt:qt,et=A[w],it="y"===w?"height":"width",nt=et+g[Z],st=et-g[tt],ot=-1!==[zt,Vt].indexOf(_),rt=null!=(J=null==x?void 0:x[w])?J:0,at=ot?nt:et-E[it]-T[it]-rt+O.altAxis,lt=ot?et+E[it]+T[it]-rt-O.altAxis:st,ct=f&&ot?function(t,e,i){var n=Ne(t,e,i);return n>i?i:n}(at,et,lt):Ne(f?at:nt,et,f?lt:st);A[w]=ct,k[w]=ct-et}e.modifiersData[n]=k}},requiresIfExists:["offset"]};function di(t,e,i){void 0===i&&(i=!1);var n,s,o=me(e),r=me(e)&&function(t){var e=t.getBoundingClientRect(),i=we(e.width)/t.offsetWidth||1,n=we(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=Le(e),l=Te(t,r,i),c={scrollLeft:0,scrollTop:0},h={x:0,y:0};return(o||!o&&!i)&&(("body"!==ue(e)||Ue(a))&&(c=(n=e)!==fe(n)&&me(n)?{scrollLeft:(s=n).scrollLeft,scrollTop:s.scrollTop}:Xe(n)),me(e)?((h=Te(e,!0)).x+=e.clientLeft,h.y+=e.clientTop):a&&(h.x=Ye(a))),{x:l.left+c.scrollLeft-h.x,y:l.top+c.scrollTop-h.y,width:l.width,height:l.height}}function ui(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var fi={placement:"bottom",modifiers:[],strategy:"absolute"};function pi(){for(var t=arguments.length,e=new Array(t),i=0;iNumber.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return(this._inNavbar||"static"===this._config.display)&&(F.setDataAttribute(this._menu,"popper","static"),t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,...g(this._config.popperConfig,[t])}}_selectMenuItem({key:t,target:e}){const i=z.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter((t=>a(t)));i.length&&b(i,e,t===Ti,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=qi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(2===t.button||"keyup"===t.type&&"Tab"!==t.key)return;const e=z.find(Ni);for(const i of e){const e=qi.getInstance(i);if(!e||!1===e._config.autoClose)continue;const n=t.composedPath(),s=n.includes(e._menu);if(n.includes(e._element)||"inside"===e._config.autoClose&&!s||"outside"===e._config.autoClose&&s)continue;if(e._menu.contains(t.target)&&("keyup"===t.type&&"Tab"===t.key||/input|select|option|textarea|form/i.test(t.target.tagName)))continue;const o={relatedTarget:e._element};"click"===t.type&&(o.clickEvent=t),e._completeHide(o)}}static dataApiKeydownHandler(t){const e=/input|textarea/i.test(t.target.tagName),i="Escape"===t.key,n=[Ei,Ti].includes(t.key);if(!n&&!i)return;if(e&&!i)return;t.preventDefault();const s=this.matches(Ii)?this:z.prev(this,Ii)[0]||z.next(this,Ii)[0]||z.findOne(Ii,t.delegateTarget.parentNode),o=qi.getOrCreateInstance(s);if(n)return t.stopPropagation(),o.show(),void o._selectMenuItem(t);o._isShown()&&(t.stopPropagation(),o.hide(),s.focus())}}N.on(document,Si,Ii,qi.dataApiKeydownHandler),N.on(document,Si,Pi,qi.dataApiKeydownHandler),N.on(document,Li,qi.clearMenus),N.on(document,Di,qi.clearMenus),N.on(document,Li,Ii,(function(t){t.preventDefault(),qi.getOrCreateInstance(this).toggle()})),m(qi);const Vi="backdrop",Ki="show",Qi=`mousedown.bs.${Vi}`,Xi={className:"modal-backdrop",clickCallback:null,isAnimated:!1,isVisible:!0,rootElement:"body"},Yi={className:"string",clickCallback:"(function|null)",isAnimated:"boolean",isVisible:"boolean",rootElement:"(element|string)"};class Ui extends H{constructor(t){super(),this._config=this._getConfig(t),this._isAppended=!1,this._element=null}static get Default(){return Xi}static get DefaultType(){return Yi}static get NAME(){return Vi}show(t){if(!this._config.isVisible)return void g(t);this._append();const e=this._getElement();this._config.isAnimated&&d(e),e.classList.add(Ki),this._emulateAnimation((()=>{g(t)}))}hide(t){this._config.isVisible?(this._getElement().classList.remove(Ki),this._emulateAnimation((()=>{this.dispose(),g(t)}))):g(t)}dispose(){this._isAppended&&(N.off(this._element,Qi),this._element.remove(),this._isAppended=!1)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_configAfterMerge(t){return t.rootElement=r(t.rootElement),t}_append(){if(this._isAppended)return;const t=this._getElement();this._config.rootElement.append(t),N.on(t,Qi,(()=>{g(this._config.clickCallback)})),this._isAppended=!0}_emulateAnimation(t){_(t,this._getElement(),this._config.isAnimated)}}const Gi=".bs.focustrap",Ji=`focusin${Gi}`,Zi=`keydown.tab${Gi}`,tn="backward",en={autofocus:!0,trapElement:null},nn={autofocus:"boolean",trapElement:"element"};class sn extends H{constructor(t){super(),this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}static get Default(){return en}static get DefaultType(){return nn}static get NAME(){return"focustrap"}activate(){this._isActive||(this._config.autofocus&&this._config.trapElement.focus(),N.off(document,Gi),N.on(document,Ji,(t=>this._handleFocusin(t))),N.on(document,Zi,(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,N.off(document,Gi))}_handleFocusin(t){const{trapElement:e}=this._config;if(t.target===document||t.target===e||e.contains(t.target))return;const i=z.focusableChildren(e);0===i.length?e.focus():this._lastTabNavDirection===tn?i[i.length-1].focus():i[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?tn:"forward")}}const on=".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",rn=".sticky-top",an="padding-right",ln="margin-right";class cn{constructor(){this._element=document.body}getWidth(){const t=document.documentElement.clientWidth;return Math.abs(window.innerWidth-t)}hide(){const t=this.getWidth();this._disableOverFlow(),this._setElementAttributes(this._element,an,(e=>e+t)),this._setElementAttributes(on,an,(e=>e+t)),this._setElementAttributes(rn,ln,(e=>e-t))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,an),this._resetElementAttributes(on,an),this._resetElementAttributes(rn,ln)}isOverflowing(){return this.getWidth()>0}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t).getPropertyValue(e);t.style.setProperty(e,`${i(Number.parseFloat(s))}px`)}))}_saveInitialAttribute(t,e){const i=t.style.getPropertyValue(e);i&&F.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=F.getDataAttribute(t,e);null!==i?(F.removeDataAttribute(t,e),t.style.setProperty(e,i)):t.style.removeProperty(e)}))}_applyManipulationCallback(t,e){if(o(t))e(t);else for(const i of z.find(t,this._element))e(i)}}const hn=".bs.modal",dn=`hide${hn}`,un=`hidePrevented${hn}`,fn=`hidden${hn}`,pn=`show${hn}`,mn=`shown${hn}`,gn=`resize${hn}`,_n=`click.dismiss${hn}`,bn=`mousedown.dismiss${hn}`,vn=`keydown.dismiss${hn}`,yn=`click${hn}.data-api`,wn="modal-open",An="show",En="modal-static",Tn={backdrop:!0,focus:!0,keyboard:!0},Cn={backdrop:"(boolean|string)",focus:"boolean",keyboard:"boolean"};class On extends W{constructor(t,e){super(t,e),this._dialog=z.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._isTransitioning=!1,this._scrollBar=new cn,this._addEventListeners()}static get Default(){return Tn}static get DefaultType(){return Cn}static get NAME(){return"modal"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||N.trigger(this._element,pn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isTransitioning=!0,this._scrollBar.hide(),document.body.classList.add(wn),this._adjustDialog(),this._backdrop.show((()=>this._showElement(t))))}hide(){this._isShown&&!this._isTransitioning&&(N.trigger(this._element,dn).defaultPrevented||(this._isShown=!1,this._isTransitioning=!0,this._focustrap.deactivate(),this._element.classList.remove(An),this._queueCallback((()=>this._hideModal()),this._element,this._isAnimated())))}dispose(){N.off(window,hn),N.off(this._dialog,hn),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new Ui({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_showElement(t){document.body.contains(this._element)||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0;const e=z.findOne(".modal-body",this._dialog);e&&(e.scrollTop=0),d(this._element),this._element.classList.add(An),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,N.trigger(this._element,mn,{relatedTarget:t})}),this._dialog,this._isAnimated())}_addEventListeners(){N.on(this._element,vn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():this._triggerBackdropTransition())})),N.on(window,gn,(()=>{this._isShown&&!this._isTransitioning&&this._adjustDialog()})),N.on(this._element,bn,(t=>{N.one(this._element,_n,(e=>{this._element===t.target&&this._element===e.target&&("static"!==this._config.backdrop?this._config.backdrop&&this.hide():this._triggerBackdropTransition())}))}))}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(wn),this._resetAdjustments(),this._scrollBar.reset(),N.trigger(this._element,fn)}))}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(N.trigger(this._element,un).defaultPrevented)return;const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._element.style.overflowY;"hidden"===e||this._element.classList.contains(En)||(t||(this._element.style.overflowY="hidden"),this._element.classList.add(En),this._queueCallback((()=>{this._element.classList.remove(En),this._queueCallback((()=>{this._element.style.overflowY=e}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;if(i&&!t){const t=p()?"paddingLeft":"paddingRight";this._element.style[t]=`${e}px`}if(!i&&t){const t=p()?"paddingRight":"paddingLeft";this._element.style[t]=`${e}px`}}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=On.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}N.on(document,yn,'[data-bs-toggle="modal"]',(function(t){const e=z.getElementFromSelector(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),N.one(e,pn,(t=>{t.defaultPrevented||N.one(e,fn,(()=>{a(this)&&this.focus()}))}));const i=z.findOne(".modal.show");i&&On.getInstance(i).hide(),On.getOrCreateInstance(e).toggle(this)})),R(On),m(On);const xn=".bs.offcanvas",kn=".data-api",Ln=`load${xn}${kn}`,Sn="show",Dn="showing",$n="hiding",In=".offcanvas.show",Nn=`show${xn}`,Pn=`shown${xn}`,Mn=`hide${xn}`,jn=`hidePrevented${xn}`,Fn=`hidden${xn}`,Hn=`resize${xn}`,Wn=`click${xn}${kn}`,Bn=`keydown.dismiss${xn}`,zn={backdrop:!0,keyboard:!0,scroll:!1},Rn={backdrop:"(boolean|string)",keyboard:"boolean",scroll:"boolean"};class qn extends W{constructor(t,e){super(t,e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get Default(){return zn}static get DefaultType(){return Rn}static get NAME(){return"offcanvas"}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||N.trigger(this._element,Nn,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._backdrop.show(),this._config.scroll||(new cn).hide(),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add(Dn),this._queueCallback((()=>{this._config.scroll&&!this._config.backdrop||this._focustrap.activate(),this._element.classList.add(Sn),this._element.classList.remove(Dn),N.trigger(this._element,Pn,{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(N.trigger(this._element,Mn).defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.add($n),this._backdrop.hide(),this._queueCallback((()=>{this._element.classList.remove(Sn,$n),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._config.scroll||(new cn).reset(),N.trigger(this._element,Fn)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_initializeBackDrop(){const t=Boolean(this._config.backdrop);return new Ui({className:"offcanvas-backdrop",isVisible:t,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:t?()=>{"static"!==this._config.backdrop?this.hide():N.trigger(this._element,jn)}:null})}_initializeFocusTrap(){return new sn({trapElement:this._element})}_addEventListeners(){N.on(this._element,Bn,(t=>{"Escape"===t.key&&(this._config.keyboard?this.hide():N.trigger(this._element,jn))}))}static jQueryInterface(t){return this.each((function(){const e=qn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}N.on(document,Wn,'[data-bs-toggle="offcanvas"]',(function(t){const e=z.getElementFromSelector(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this))return;N.one(e,Fn,(()=>{a(this)&&this.focus()}));const i=z.findOne(In);i&&i!==e&&qn.getInstance(i).hide(),qn.getOrCreateInstance(e).toggle(this)})),N.on(window,Ln,(()=>{for(const t of z.find(In))qn.getOrCreateInstance(t).show()})),N.on(window,Hn,(()=>{for(const t of z.find("[aria-modal][class*=show][class*=offcanvas-]"))"fixed"!==getComputedStyle(t).position&&qn.getOrCreateInstance(t).hide()})),R(qn),m(qn);const Vn={"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},Kn=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Qn=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:/?#]*(?:[/?#]|$))/i,Xn=(t,e)=>{const i=t.nodeName.toLowerCase();return e.includes(i)?!Kn.has(i)||Boolean(Qn.test(t.nodeValue)):e.filter((t=>t instanceof RegExp)).some((t=>t.test(i)))},Yn={allowList:Vn,content:{},extraClass:"",html:!1,sanitize:!0,sanitizeFn:null,template:"
    "},Un={allowList:"object",content:"object",extraClass:"(string|function)",html:"boolean",sanitize:"boolean",sanitizeFn:"(null|function)",template:"string"},Gn={entry:"(string|element|function|null)",selector:"(string|element)"};class Jn extends H{constructor(t){super(),this._config=this._getConfig(t)}static get Default(){return Yn}static get DefaultType(){return Un}static get NAME(){return"TemplateFactory"}getContent(){return Object.values(this._config.content).map((t=>this._resolvePossibleFunction(t))).filter(Boolean)}hasContent(){return this.getContent().length>0}changeContent(t){return this._checkContent(t),this._config.content={...this._config.content,...t},this}toHtml(){const t=document.createElement("div");t.innerHTML=this._maybeSanitize(this._config.template);for(const[e,i]of Object.entries(this._config.content))this._setContent(t,i,e);const e=t.children[0],i=this._resolvePossibleFunction(this._config.extraClass);return i&&e.classList.add(...i.split(" ")),e}_typeCheckConfig(t){super._typeCheckConfig(t),this._checkContent(t.content)}_checkContent(t){for(const[e,i]of Object.entries(t))super._typeCheckConfig({selector:e,entry:i},Gn)}_setContent(t,e,i){const n=z.findOne(i,t);n&&((e=this._resolvePossibleFunction(e))?o(e)?this._putElementInTemplate(r(e),n):this._config.html?n.innerHTML=this._maybeSanitize(e):n.textContent=e:n.remove())}_maybeSanitize(t){return this._config.sanitize?function(t,e,i){if(!t.length)return t;if(i&&"function"==typeof i)return i(t);const n=(new window.DOMParser).parseFromString(t,"text/html"),s=[].concat(...n.body.querySelectorAll("*"));for(const t of s){const i=t.nodeName.toLowerCase();if(!Object.keys(e).includes(i)){t.remove();continue}const n=[].concat(...t.attributes),s=[].concat(e["*"]||[],e[i]||[]);for(const e of n)Xn(e,s)||t.removeAttribute(e.nodeName)}return n.body.innerHTML}(t,this._config.allowList,this._config.sanitizeFn):t}_resolvePossibleFunction(t){return g(t,[this])}_putElementInTemplate(t,e){if(this._config.html)return e.innerHTML="",void e.append(t);e.textContent=t.textContent}}const Zn=new Set(["sanitize","allowList","sanitizeFn"]),ts="fade",es="show",is=".modal",ns="hide.bs.modal",ss="hover",os="focus",rs={AUTO:"auto",TOP:"top",RIGHT:p()?"left":"right",BOTTOM:"bottom",LEFT:p()?"right":"left"},as={allowList:Vn,animation:!0,boundary:"clippingParents",container:!1,customClass:"",delay:0,fallbackPlacements:["top","right","bottom","left"],html:!1,offset:[0,6],placement:"top",popperConfig:null,sanitize:!0,sanitizeFn:null,selector:!1,template:'',title:"",trigger:"hover focus"},ls={allowList:"object",animation:"boolean",boundary:"(string|element)",container:"(string|element|boolean)",customClass:"(string|function)",delay:"(number|object)",fallbackPlacements:"array",html:"boolean",offset:"(array|string|function)",placement:"(string|function)",popperConfig:"(null|object|function)",sanitize:"boolean",sanitizeFn:"(null|function)",selector:"(string|boolean)",template:"string",title:"(string|element|function)",trigger:"string"};class cs extends W{constructor(t,e){if(void 0===vi)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t,e),this._isEnabled=!0,this._timeout=0,this._isHovered=null,this._activeTrigger={},this._popper=null,this._templateFactory=null,this._newContent=null,this.tip=null,this._setListeners(),this._config.selector||this._fixTitle()}static get Default(){return as}static get DefaultType(){return ls}static get NAME(){return"tooltip"}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(){this._isEnabled&&(this._activeTrigger.click=!this._activeTrigger.click,this._isShown()?this._leave():this._enter())}dispose(){clearTimeout(this._timeout),N.off(this._element.closest(is),ns,this._hideModalHandler),this._element.getAttribute("data-bs-original-title")&&this._element.setAttribute("title",this._element.getAttribute("data-bs-original-title")),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this._isWithContent()||!this._isEnabled)return;const t=N.trigger(this._element,this.constructor.eventName("show")),e=(c(this._element)||this._element.ownerDocument.documentElement).contains(this._element);if(t.defaultPrevented||!e)return;this._disposePopper();const i=this._getTipElement();this._element.setAttribute("aria-describedby",i.getAttribute("id"));const{container:n}=this._config;if(this._element.ownerDocument.documentElement.contains(this.tip)||(n.append(i),N.trigger(this._element,this.constructor.eventName("inserted"))),this._popper=this._createPopper(i),i.classList.add(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.on(t,"mouseover",h);this._queueCallback((()=>{N.trigger(this._element,this.constructor.eventName("shown")),!1===this._isHovered&&this._leave(),this._isHovered=!1}),this.tip,this._isAnimated())}hide(){if(this._isShown()&&!N.trigger(this._element,this.constructor.eventName("hide")).defaultPrevented){if(this._getTipElement().classList.remove(es),"ontouchstart"in document.documentElement)for(const t of[].concat(...document.body.children))N.off(t,"mouseover",h);this._activeTrigger.click=!1,this._activeTrigger[os]=!1,this._activeTrigger[ss]=!1,this._isHovered=null,this._queueCallback((()=>{this._isWithActiveTrigger()||(this._isHovered||this._disposePopper(),this._element.removeAttribute("aria-describedby"),N.trigger(this._element,this.constructor.eventName("hidden")))}),this.tip,this._isAnimated())}}update(){this._popper&&this._popper.update()}_isWithContent(){return Boolean(this._getTitle())}_getTipElement(){return this.tip||(this.tip=this._createTipElement(this._newContent||this._getContentForTemplate())),this.tip}_createTipElement(t){const e=this._getTemplateFactory(t).toHtml();if(!e)return null;e.classList.remove(ts,es),e.classList.add(`bs-${this.constructor.NAME}-auto`);const i=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME).toString();return e.setAttribute("id",i),this._isAnimated()&&e.classList.add(ts),e}setContent(t){this._newContent=t,this._isShown()&&(this._disposePopper(),this.show())}_getTemplateFactory(t){return this._templateFactory?this._templateFactory.changeContent(t):this._templateFactory=new Jn({...this._config,content:t,extraClass:this._resolvePossibleFunction(this._config.customClass)}),this._templateFactory}_getContentForTemplate(){return{".tooltip-inner":this._getTitle()}}_getTitle(){return this._resolvePossibleFunction(this._config.title)||this._element.getAttribute("data-bs-original-title")}_initializeOnDelegatedTarget(t){return this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_isAnimated(){return this._config.animation||this.tip&&this.tip.classList.contains(ts)}_isShown(){return this.tip&&this.tip.classList.contains(es)}_createPopper(t){const e=g(this._config.placement,[this,t,this._element]),i=rs[e.toUpperCase()];return bi(this._element,t,this._getPopperConfig(i))}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return g(t,[this._element])}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"preSetPlacement",enabled:!0,phase:"beforeMain",fn:t=>{this._getTipElement().setAttribute("data-popper-placement",t.state.placement)}}]};return{...e,...g(this._config.popperConfig,[e])}}_setListeners(){const t=this._config.trigger.split(" ");for(const e of t)if("click"===e)N.on(this._element,this.constructor.eventName("click"),this._config.selector,(t=>{this._initializeOnDelegatedTarget(t).toggle()}));else if("manual"!==e){const t=e===ss?this.constructor.eventName("mouseenter"):this.constructor.eventName("focusin"),i=e===ss?this.constructor.eventName("mouseleave"):this.constructor.eventName("focusout");N.on(this._element,t,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusin"===t.type?os:ss]=!0,e._enter()})),N.on(this._element,i,this._config.selector,(t=>{const e=this._initializeOnDelegatedTarget(t);e._activeTrigger["focusout"===t.type?os:ss]=e._element.contains(t.relatedTarget),e._leave()}))}this._hideModalHandler=()=>{this._element&&this.hide()},N.on(this._element.closest(is),ns,this._hideModalHandler)}_fixTitle(){const t=this._element.getAttribute("title");t&&(this._element.getAttribute("aria-label")||this._element.textContent.trim()||this._element.setAttribute("aria-label",t),this._element.setAttribute("data-bs-original-title",t),this._element.removeAttribute("title"))}_enter(){this._isShown()||this._isHovered?this._isHovered=!0:(this._isHovered=!0,this._setTimeout((()=>{this._isHovered&&this.show()}),this._config.delay.show))}_leave(){this._isWithActiveTrigger()||(this._isHovered=!1,this._setTimeout((()=>{this._isHovered||this.hide()}),this._config.delay.hide))}_setTimeout(t,e){clearTimeout(this._timeout),this._timeout=setTimeout(t,e)}_isWithActiveTrigger(){return Object.values(this._activeTrigger).includes(!0)}_getConfig(t){const e=F.getDataAttributes(this._element);for(const t of Object.keys(e))Zn.has(t)&&delete e[t];return t={...e,..."object"==typeof t&&t?t:{}},t=this._mergeConfigObj(t),t=this._configAfterMerge(t),this._typeCheckConfig(t),t}_configAfterMerge(t){return t.container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),t}_getDelegateConfig(){const t={};for(const[e,i]of Object.entries(this._config))this.constructor.Default[e]!==i&&(t[e]=i);return t.selector=!1,t.trigger="manual",t}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null),this.tip&&(this.tip.remove(),this.tip=null)}static jQueryInterface(t){return this.each((function(){const e=cs.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(cs);const hs={...cs.Default,content:"",offset:[0,8],placement:"right",template:'',trigger:"click"},ds={...cs.DefaultType,content:"(null|string|element|function)"};class us extends cs{static get Default(){return hs}static get DefaultType(){return ds}static get NAME(){return"popover"}_isWithContent(){return this._getTitle()||this._getContent()}_getContentForTemplate(){return{".popover-header":this._getTitle(),".popover-body":this._getContent()}}_getContent(){return this._resolvePossibleFunction(this._config.content)}static jQueryInterface(t){return this.each((function(){const e=us.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}m(us);const fs=".bs.scrollspy",ps=`activate${fs}`,ms=`click${fs}`,gs=`load${fs}.data-api`,_s="active",bs="[href]",vs=".nav-link",ys=`${vs}, .nav-item > ${vs}, .list-group-item`,ws={offset:null,rootMargin:"0px 0px -25%",smoothScroll:!1,target:null,threshold:[.1,.5,1]},As={offset:"(number|null)",rootMargin:"string",smoothScroll:"boolean",target:"element",threshold:"array"};class Es extends W{constructor(t,e){super(t,e),this._targetLinks=new Map,this._observableSections=new Map,this._rootElement="visible"===getComputedStyle(this._element).overflowY?null:this._element,this._activeTarget=null,this._observer=null,this._previousScrollData={visibleEntryTop:0,parentScrollTop:0},this.refresh()}static get Default(){return ws}static get DefaultType(){return As}static get NAME(){return"scrollspy"}refresh(){this._initializeTargetsAndObservables(),this._maybeEnableSmoothScroll(),this._observer?this._observer.disconnect():this._observer=this._getNewObserver();for(const t of this._observableSections.values())this._observer.observe(t)}dispose(){this._observer.disconnect(),super.dispose()}_configAfterMerge(t){return t.target=r(t.target)||document.body,t.rootMargin=t.offset?`${t.offset}px 0px -30%`:t.rootMargin,"string"==typeof t.threshold&&(t.threshold=t.threshold.split(",").map((t=>Number.parseFloat(t)))),t}_maybeEnableSmoothScroll(){this._config.smoothScroll&&(N.off(this._config.target,ms),N.on(this._config.target,ms,bs,(t=>{const e=this._observableSections.get(t.target.hash);if(e){t.preventDefault();const i=this._rootElement||window,n=e.offsetTop-this._element.offsetTop;if(i.scrollTo)return void i.scrollTo({top:n,behavior:"smooth"});i.scrollTop=n}})))}_getNewObserver(){const t={root:this._rootElement,threshold:this._config.threshold,rootMargin:this._config.rootMargin};return new IntersectionObserver((t=>this._observerCallback(t)),t)}_observerCallback(t){const e=t=>this._targetLinks.get(`#${t.target.id}`),i=t=>{this._previousScrollData.visibleEntryTop=t.target.offsetTop,this._process(e(t))},n=(this._rootElement||document.documentElement).scrollTop,s=n>=this._previousScrollData.parentScrollTop;this._previousScrollData.parentScrollTop=n;for(const o of t){if(!o.isIntersecting){this._activeTarget=null,this._clearActiveClass(e(o));continue}const t=o.target.offsetTop>=this._previousScrollData.visibleEntryTop;if(s&&t){if(i(o),!n)return}else s||t||i(o)}}_initializeTargetsAndObservables(){this._targetLinks=new Map,this._observableSections=new Map;const t=z.find(bs,this._config.target);for(const e of t){if(!e.hash||l(e))continue;const t=z.findOne(decodeURI(e.hash),this._element);a(t)&&(this._targetLinks.set(decodeURI(e.hash),e),this._observableSections.set(e.hash,t))}}_process(t){this._activeTarget!==t&&(this._clearActiveClass(this._config.target),this._activeTarget=t,t.classList.add(_s),this._activateParents(t),N.trigger(this._element,ps,{relatedTarget:t}))}_activateParents(t){if(t.classList.contains("dropdown-item"))z.findOne(".dropdown-toggle",t.closest(".dropdown")).classList.add(_s);else for(const e of z.parents(t,".nav, .list-group"))for(const t of z.prev(e,ys))t.classList.add(_s)}_clearActiveClass(t){t.classList.remove(_s);const e=z.find(`${bs}.${_s}`,t);for(const t of e)t.classList.remove(_s)}static jQueryInterface(t){return this.each((function(){const e=Es.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(window,gs,(()=>{for(const t of z.find('[data-bs-spy="scroll"]'))Es.getOrCreateInstance(t)})),m(Es);const Ts=".bs.tab",Cs=`hide${Ts}`,Os=`hidden${Ts}`,xs=`show${Ts}`,ks=`shown${Ts}`,Ls=`click${Ts}`,Ss=`keydown${Ts}`,Ds=`load${Ts}`,$s="ArrowLeft",Is="ArrowRight",Ns="ArrowUp",Ps="ArrowDown",Ms="Home",js="End",Fs="active",Hs="fade",Ws="show",Bs=":not(.dropdown-toggle)",zs='[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',Rs=`.nav-link${Bs}, .list-group-item${Bs}, [role="tab"]${Bs}, ${zs}`,qs=`.${Fs}[data-bs-toggle="tab"], .${Fs}[data-bs-toggle="pill"], .${Fs}[data-bs-toggle="list"]`;class Vs extends W{constructor(t){super(t),this._parent=this._element.closest('.list-group, .nav, [role="tablist"]'),this._parent&&(this._setInitialAttributes(this._parent,this._getChildren()),N.on(this._element,Ss,(t=>this._keydown(t))))}static get NAME(){return"tab"}show(){const t=this._element;if(this._elemIsActive(t))return;const e=this._getActiveElem(),i=e?N.trigger(e,Cs,{relatedTarget:t}):null;N.trigger(t,xs,{relatedTarget:e}).defaultPrevented||i&&i.defaultPrevented||(this._deactivate(e,t),this._activate(t,e))}_activate(t,e){t&&(t.classList.add(Fs),this._activate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.removeAttribute("tabindex"),t.setAttribute("aria-selected",!0),this._toggleDropDown(t,!0),N.trigger(t,ks,{relatedTarget:e})):t.classList.add(Ws)}),t,t.classList.contains(Hs)))}_deactivate(t,e){t&&(t.classList.remove(Fs),t.blur(),this._deactivate(z.getElementFromSelector(t)),this._queueCallback((()=>{"tab"===t.getAttribute("role")?(t.setAttribute("aria-selected",!1),t.setAttribute("tabindex","-1"),this._toggleDropDown(t,!1),N.trigger(t,Os,{relatedTarget:e})):t.classList.remove(Ws)}),t,t.classList.contains(Hs)))}_keydown(t){if(![$s,Is,Ns,Ps,Ms,js].includes(t.key))return;t.stopPropagation(),t.preventDefault();const e=this._getChildren().filter((t=>!l(t)));let i;if([Ms,js].includes(t.key))i=e[t.key===Ms?0:e.length-1];else{const n=[Is,Ps].includes(t.key);i=b(e,t.target,n,!0)}i&&(i.focus({preventScroll:!0}),Vs.getOrCreateInstance(i).show())}_getChildren(){return z.find(Rs,this._parent)}_getActiveElem(){return this._getChildren().find((t=>this._elemIsActive(t)))||null}_setInitialAttributes(t,e){this._setAttributeIfNotExists(t,"role","tablist");for(const t of e)this._setInitialAttributesOnChild(t)}_setInitialAttributesOnChild(t){t=this._getInnerElement(t);const e=this._elemIsActive(t),i=this._getOuterElement(t);t.setAttribute("aria-selected",e),i!==t&&this._setAttributeIfNotExists(i,"role","presentation"),e||t.setAttribute("tabindex","-1"),this._setAttributeIfNotExists(t,"role","tab"),this._setInitialAttributesOnTargetPanel(t)}_setInitialAttributesOnTargetPanel(t){const e=z.getElementFromSelector(t);e&&(this._setAttributeIfNotExists(e,"role","tabpanel"),t.id&&this._setAttributeIfNotExists(e,"aria-labelledby",`${t.id}`))}_toggleDropDown(t,e){const i=this._getOuterElement(t);if(!i.classList.contains("dropdown"))return;const n=(t,n)=>{const s=z.findOne(t,i);s&&s.classList.toggle(n,e)};n(".dropdown-toggle",Fs),n(".dropdown-menu",Ws),i.setAttribute("aria-expanded",e)}_setAttributeIfNotExists(t,e,i){t.hasAttribute(e)||t.setAttribute(e,i)}_elemIsActive(t){return t.classList.contains(Fs)}_getInnerElement(t){return t.matches(Rs)?t:z.findOne(Rs,t)}_getOuterElement(t){return t.closest(".nav-item, .list-group-item")||t}static jQueryInterface(t){return this.each((function(){const e=Vs.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t]()}}))}}N.on(document,Ls,zs,(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),l(this)||Vs.getOrCreateInstance(this).show()})),N.on(window,Ds,(()=>{for(const t of z.find(qs))Vs.getOrCreateInstance(t)})),m(Vs);const Ks=".bs.toast",Qs=`mouseover${Ks}`,Xs=`mouseout${Ks}`,Ys=`focusin${Ks}`,Us=`focusout${Ks}`,Gs=`hide${Ks}`,Js=`hidden${Ks}`,Zs=`show${Ks}`,to=`shown${Ks}`,eo="hide",io="show",no="showing",so={animation:"boolean",autohide:"boolean",delay:"number"},oo={animation:!0,autohide:!0,delay:5e3};class ro extends W{constructor(t,e){super(t,e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get Default(){return oo}static get DefaultType(){return so}static get NAME(){return"toast"}show(){N.trigger(this._element,Zs).defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(eo),d(this._element),this._element.classList.add(io,no),this._queueCallback((()=>{this._element.classList.remove(no),N.trigger(this._element,to),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this.isShown()&&(N.trigger(this._element,Gs).defaultPrevented||(this._element.classList.add(no),this._queueCallback((()=>{this._element.classList.add(eo),this._element.classList.remove(no,io),N.trigger(this._element,Js)}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this.isShown()&&this._element.classList.remove(io),super.dispose()}isShown(){return this._element.classList.contains(io)}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){N.on(this._element,Qs,(t=>this._onInteraction(t,!0))),N.on(this._element,Xs,(t=>this._onInteraction(t,!1))),N.on(this._element,Ys,(t=>this._onInteraction(t,!0))),N.on(this._element,Us,(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=ro.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(ro),m(ro),{Alert:Q,Button:Y,Carousel:xt,Collapse:Bt,Dropdown:qi,Modal:On,Offcanvas:qn,Popover:us,ScrollSpy:Es,Tab:Vs,Toast:ro,Tooltip:cs}})); //# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/docs/site_libs/clipboard/clipboard.min.js b/docs/site_libs/clipboard/clipboard.min.js index 41c6a0f..1103f81 100644 --- a/docs/site_libs/clipboard/clipboard.min.js +++ b/docs/site_libs/clipboard/clipboard.min.js @@ -1,7 +1,7 @@ /*! - * clipboard.js v2.0.10 + * clipboard.js v2.0.11 * https://clipboardjs.com/ * * Licensed MIT © Zeno Rocha */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={686:function(t,e,n){"use strict";n.d(e,{default:function(){return o}});var e=n(279),i=n.n(e),e=n(370),u=n.n(e),e=n(817),c=n.n(e);function a(t){try{return document.execCommand(t)}catch(t){return}}var f=function(t){t=c()(t);return a("cut"),t};var l=function(t){var e,n,o,r=1").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
    ").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/docs/site_libs/jquery-3.6.0/jquery-3.6.0.js b/docs/site_libs/jquery-3.6.0/jquery-3.6.0.js new file mode 100644 index 0000000..fc6c299 --- /dev/null +++ b/docs/site_libs/jquery-3.6.0/jquery-3.6.0.js @@ -0,0 +1,10881 @@ +/*! + * jQuery JavaScript Library v3.6.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright OpenJS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2021-03-02T17:08Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 + // Plus for old WebKit, typeof returns "function" for HTML collections + // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) + return typeof obj === "function" && typeof obj.nodeType !== "number" && + typeof obj.item !== "function"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.6.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), + function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); + } ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.6 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2021-02-16 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the primary Deferred + primary = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + primary.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( primary.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return primary.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); + } + + return primary.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
    " ], + col: [ 2, "", "
    " ], + tr: [ 2, "", "
    " ], + td: [ 3, "", "
    " ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + + // Support: Chrome 86+ + // In Chrome, if an element having a focusout handler is blurred by + // clicking outside of it, it invokes the handler synchronously. If + // that handler calls `.remove()` on the element, the data is cleared, + // leaving `result` undefined. We need to guard against this. + return result && result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + which: true +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + // Suppress native focus or blur as it's already being fired + // in leverageNative. + _default: function() { + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + // + // Support: Firefox 70+ + // Only Firefox includes border widths + // in computed dimensions. (gh-4529) + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; + tr.style.cssText = "border:1px solid"; + + // Support: Chrome 86+ + // Height set through cssText does not get applied. + // Computed height then comes back as 0. + tr.style.height = "1px"; + trChild.style.height = "9px"; + + // Support: Android 8 Chrome 86+ + // In our bodyBackground.html iframe, + // display for all div elements is set to "inline", + // which causes a problem only in Android 8 Chrome 86. + // Ensuring the div is display: block + // gets around this issue. + trChild.style.display = "block"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + + parseInt( trStyle.borderTopWidth, 10 ) + + parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} + + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ).filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ).map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + +originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script but not if jsonp + if ( !isSuccess && + jQuery.inArray( "script", s.dataTypes ) > -1 && + jQuery.inArray( "json", s.dataTypes ) < 0 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( "